今天来总结一下String在jvm内存中的一些分配及其一些优化,包括最新的java8 update20才加入的deduplication特性,还有尚未发布的java 9中的compact特性
jvm中对象存在heap中,还有一块叫做String pool 的地方存放指向heap中字符串对象的指针
String s1 = "hello java";
String s2 = "hello java";
System.out.println(s1 == s2); //true 说明s1和s2指向同一块内存地址
下面解释s1和s2的分配过程,
首先String s1 = "hello java"; ,这个是给s1分配字面值,jvm会先判断常量池中有没有引用指向堆中的一个字符串hello java,
如果没有就在堆中分配对象,然后线程堆栈的变量s1指向堆中的String对象,并且在String pool中维护一个引用指向这个String对象,
然后String s2 = "hello java",这个时候重复之前的过程,但是jvm发现常量池中已经有一个引用指向了堆中包含这个字面值的String对象,就直接在线程栈中分配了指向堆的新的引用S2,
结果如图下
接下来看通过构造函数创建String
String s3 = new String("hello java");
System.out.println(s3 == s2); //false 说明s3和s2 不是指向同一块内存地址
s3是用new创建的对象,这个时候会忽略常量池中的引用,而直接在堆中新建一个对象,这种是不推荐的,下面是s3后的内存图
下面无论通过s3还是s1进行intern,返回的引用都是和s1相同,所以如果常量池中如果存在的话,intern返回的是引用和常量池中表记录的引用是一样的
String s4 = s3.intern();
String s5 = s1.intern();
System.out.println(s4 == s1); //true
System.out.println(s5 == s1); //true
//上面的图没有话,s4,s5指向的String对象和s1,s2一样
java 8 update 20中如果加入-XX:+UseG1GC -XX:+UseStringDeduplication ,jvm将启动一个叫做Deduplication的优化,如果两个String对象中的char数组是一样的,则会回收掉其中一个,使两个对象share同一个char数组,注意必须启动java7新增的G1回收器
Deduplication和String intern不一样,前者是重用的char数组,并不会减少heap中的String对象数目
而后者可以直接重用现有的String对象
有关统计,大型java应用中25%的heap内存由String对象消耗,有近一半的String对象是重复的,也就是通过String的equals是返回true的,G1 garbage collector 会减少这种内存浪费
java9中新增的compcact机制可以让java中的char不要总是消耗2个byte,而是视情况而定,因为大多数情况其实一个char只需要8个bit就可以,也就是一个byte
参考如下:
http://openjdk.java.net/jeps/192
http://openjdk.java.net/jeps/254
后续可以通过分析字节码,利用java profile工具,分析gc log日志等方式更加细节的分析此类内存情况