您现在的位置是:首页 > 文章详情

内存优化篇-String/char[]/byte[]的选择

日期:2019-01-23点击:337

Java基本数据类型的大小

type

size(bits)

bytes

boolean

8

1

byte

8

1

char

16

2

short

16

2

int

32

4

long

64

8

float

32

4

double

64

8

Java引用的大小

在 32 位的 JVM 上,一个对象引用占用 4 个字节;在 64 位JVM上,占用 8 个字节。

使用 8 个字节是为了能够管理大于 4G 的内存,如果你的程序不需要访问大于 4G 的内存,

可通过 -XX:+UseCompressedOops 选项,开启指针压缩。从 Java 1.6.0_23 开始,这个选项默认是开的。


Java对象头的大小

在32位JVM中,对象头的大小为8个字节(4字节的Mark Word+4字节的Klass Pointer).

在64位JVM上,占用16个字节(8字节的Mark Word+8字节的Klass Pointer,因为开启UseCompressedOops,所以实际占用12个字节(8字节的Mark Word+4字节的Klass Pointer) 。参考klass pointer


接下来的内容都基于64位的JVM来展开


Java对象的大小

1、任意Java对象都包含至少12个字节的Object Header。

2、JVM分配内存以8字节为基本单位,如果不满小于8字节,则向8字节的倍数补齐。参考8 byte alignment


思考   

Object object = new Object(); 占用多少内存?

数组的大小如何计算?


验证

添加Maven依赖


<dependency> <groupId>org.openjdk.jol</groupId> <artifactId>jol-core</artifactId> <version>0.9</version> </dependency>


import org.openjdk.jol.info.ClassLayout; /** * Created by jianpingpan on 2019/1/17. */ public class BasicClass { public static void main(String[] args) throws Exception { System.out.println(ClassLayout.parseClass(Object.class).toPrintable()); System.out.println(ClassLayout.parseClass(String.class).toPrintable()); System.out.println(ClassLayout.parseClass(byte[].class).toPrintable()); System.out.println(ClassLayout.parseClass(char[].class).toPrintable()); } }





1547711148456-a226b348-168e-45ed-9265-52

byte[] 和char[]的 object header为16个字节是因为有4个字节的数组长度。



String /  char[]   / byte[] 内存大小计算


 String a = new String("abc"); String b = new String("abcd"); String c = new String("abc");



1547713532558-2389799f-3057-4ebd-bae0-11

第一行占用JVM内存的大小:

对象大小 = 12字节(object header)+

                   4字节 (hash)+

                   4字节(数组引用vlaue[]) +

                   4字节 (padding)

                   16字节+3*2字节+2字节padding   (数组value[])

              =   48字节


假设要缓存的字符个数为N。


String的内存大小计算公式 = 40+N*2  +padding

char数组的内存大小计算公式 = 16+N*2+padding


如果用byte数组来存储字符串数据,占用的内存大小X需要分2种情况讨论:


1、如果需要存储的字符全在ASCII码中,一个字符用一个byte就可以存储 (编码方式可选ISO-8859-1/GBK/UTF-8):


X = 16+N+padding


2、如果需要存储的字符范围不能被ASCII码覆盖,则需要根据字符范围确定合适的存储方式。

如需要要存储字符集为ASCII+中文字符,则可使用GBK编码:


16+N+padding <X <  16+N*2+padding


如果字符集不能被ASCII码覆盖,并且包含非中文字符,则使用UTF-8编码:


16+N+padding<X<16+6*N+padding


结论:

由此可见,char数组占用的内存大小小于String占用的内存大小。

若存储的字符范围以ASCII码为主,使用byte数组存储优于char数组。



实际使用场景

那么在缓存中可以直接用char[]或byte[]替换String么?          

Set<String> set = new HashSet<>();

替换成

Set<byte[]> set = new HashSet<>();

会怎样呢?


很明显,contains方法、get方法都会失效。因为每个byte[]的hashCode不一样。


我们用下面的这个ByteArray/CharArray封装byte[],再用ByteArray替换String。


/** * Created by jianpingpan on 2019/1/23. */ public class ByteArray { byte[] bytes; public ByteArray(byte[] bytes){ this.bytes = bytes; } @Override public int hashCode() { if(null == bytes){ return 0; } return new String(bytes).hashCode(); } @Override public boolean equals(Object obj) { if(obj == null){ return false; } return hashCode()==obj.hashCode(); } }



(CharArray的实现方式同ByteArray,只是把byte[] bytes 替换成 char[] chars即可)


ByteArray占用的内存大小 = 

                  12字节(object header+

                   4字节(数组引用bytes[]) +

                   16字节+N字节+padding   (数组bytes[]

              =   32字节+N字节+padding


CharArray占用的内存大小=

                   12字节(object header+

                   4字节(数组引用bytes[]) +

                   16字节+2*N字节+padding   (数组bytes[]

              =   32字节+2*N字节+padding


其中,N为数组中元素的个数。


例子

以存储100万条长度为32位的MD5字符串为例且内容互不相同字符串为例(假设字符串中的字符均为字母、数字、下划线)。

 

可以用classmexer来计算内存使用量 。

  

import com.javamex.classmexer.MemoryUtil; /** * Created by jianpingpan on 2019/1/25. */ public class StringTest { public static void main(String[] args){ String s="cfcd208495d565ef66e7dff9f98764da"; ByteArray b = new ByteArray(s.getBytes()); CharArray c = new CharArray(s.toCharArray()); long stringBytes = MemoryUtil.deepMemoryUsageOf(s); long byteArrayBytes = MemoryUtil.deepMemoryUsageOf(b); long charArrayBytes = MemoryUtil.deepMemoryUsageOf(c); System.out.println("stringBytes:"+stringBytes); System.out.println("byteArrayBytes:"+byteArrayBytes); System.out.println("charArrayBytes:"+charArrayBytes); } }


resize,w_2000

 

用String存储,每条记录占用的空间为 40+32*2 = 104字节

用ByteArray存储,每条记录占用的空间为 32+32 = 64字节

用CharArray存储,每条记录占用的空间为 32+32*2 = 96字节




                  



参考文档:

http://btoddb-java-sizing.blogspot.com/2012/01/object-sizes.html

https://stackoverflow.com/questions/26357186/what-is-in-java-object-header/26416983

http://openjdk.java.net/groups/hotspot/docs/HotSpotGlossary.html

原文链接:https://yq.aliyun.com/articles/688740
关注公众号

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。

持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。

转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。

文章评论

共有0条评论来说两句吧...

文章二维码

扫描即可查看该文章

点击排行

推荐阅读

最新文章