继续了自己研究String相关问题的思路和过程。不敢保证绝对正确,仅仅是一个分析过程。有出错的地方希望大家指出~多交流分享。
问题的出发点
在网上看到一道题:
String str = new String("abc");
问其中生成了多少个String对象?
答案写的是两个。"abc"本身是一个,而new又生成了一个。
探究开始
1.“abc”是什么?似乎不是在heap中的String对象?
我查看了这句代码的bytecode,却发现有些奇怪,如下:
NEW String DUP LDC "abc" INVOKESPECIAL String.<init>(String) : void ASTORE 1
String的构造函数 调用命令实际使用的就是String类型作为参数,那么,栈上应该有一个 String类型的索引。
"abc"并不是直接以object存在的,而是使用LDC命令调用。也就是说,它实际存在于constant pool的索引中。
指令ldc indexbyte 的含义:将两字节的值从indexbyte索引的constant pool中的项中 push到方法栈上。(from Inside the Java2 Virtual machine)。
由此推论,在字节码中,ldc命令在constant pool中找到了能索引到abc那个String对象的索引值。
2.什么是constant pool
翻阅了Inside the JAVA2 virtual machine 和 Programming for the java virtual machine ,仔细查看了这个constant pool的信息。
constant pool是 .class 文件中的一部分,记录了许多constant信息,索引的字符串信息。由于java是动态加载的,.class文件并没有包含程序运行时的内存布局,method的调用等无法直接记录出物理位置,constant pool通过索引的方法解决了这个问题。
Constant Pool中拥有很多不同的表,其中Constant_Utf8_info table中,记录着“string literals that get instantiated as String objects”。
而在String 的java doc中,有对String literal的说明:
All string literals in Java programs, such as"abc"
, are implemented as instances of this class.
结合这两句话,我们可以理解为,在java编译成.class文件的过程中,确定下来的String literal 都先被优化记录在 constant pool中(那些双引号字符串,都是以CONSTANT_utf8_info的形式存储在constant pool中的)。也就是说,java源代码文件中出现的那些诸如"abc"字符串,都已经被提前放在了constant pool中。
可以参考wiki中对于String interning的资料。
这段代码能证明这一点。
public class Program { public static void main(String[] args) { String str1 = "Hello"; String str2 = "Hello"; System.out.print(str1 == str2); } }
结果是true.
3.constant pool是静态的存在于.class文件的,运行时又怎样?
但是constant pool仅仅是编译时产生的,对于运行时的情况怎么样呢?
Inside the Java2 Virtual Machine 中有看到一句话,"notice that every class has its own run time constant pool".
run time constant pool 其实就是对应于.class文件中constant pool在运行时JVM上的表现形式。
我在String 的java doc中又发现了一个有趣的method:intern(),我翻译如下(翻译的不好,见谅):
当intern方法被调用,如果池中已经拥有一个与该String的字符串值相等(即equals()调用后为true)的String对象时,那么池中的那个String对象会被返回。否则,池中会增加这个对象,并返回当前这个String对象。
其中有介绍一个String pool的东西,
字符串池(String pool),初始是空的,由类Class私有的控制。
到此,我们可以看到,String pool其实是类似于线程池的缓冲器,可以起到节约内存的作用。如下代码可以验证
package biaobiaoqi.thinkingInJava; public class Test { public static void main(String[] args){ String strA1 = "ab"; String strA2 = "c"; String strB1 = "a"; String strB2 = "bc" ; System.out.println((strA1+strA2).intern() == (strB1 + strB2).intern()); } }
结果为true.
至于String Pool到底是怎么样的,暂时没有搞清楚。
4.是否每个class文件的run time constant pool都拥有一个String pool呢?
逻辑上讲,一个JVM拥有一个string pool是比较合理的,因为只有那样,才能尽可能的实现String对象的重用。
我查看了java.lang.String的源代码,发现Intern()方法是一个native方法,即本地实现的方法,而不是一个java方法,这让我们不能直观的看到Strng pool的实现细节。
之后又继续查阅了 关于String Interning方面的知识。发现,原来关于String Intering的那个String pool是在Permanent Generation。
后期的JVM实现里,将heap划分为三部分,permanent gen 是用来存储 Class对象和String pool等的 。
(参见http://en.wikipedia.org/wiki/Java_Virtual_Machine#Heap)
说白了,Permanent Generation 就是heap的一部分,只是为了Garbage Collection的方便,将heap按生命周期划分为了young generation 、tenured generation(old generation)和permanent generation( permgen ). 考虑到String pool为了解决字符串的重复问题,生命周期长,就把它放到了permgen。
回过头来看看文章刚开始的那个问题。
String str = new String("abc"); 这里确实是有两个String对象生成了。但是,要知道String对象的背后还有个String pool在支撑着呢!
整理下思路:
编译java源代码时,源文件中出现的双引号内的字符串都被收纳到 constant pool中,用CONSTANT_utf8_info项存储着。
JVM中,相应的类被加载运行后,constant pool 对应的映射到JVM的runtime constant pool。其中每项CONSTANT_utf8_info(也就试记录那些字符串的)都会在常量引用解析时,自动生成相应的internal String,记录在String pool中,也就试存在于heap中的 PermGen里。
使用String str = new String("xxx")来创建String 对象会在 heap中重新生成新的String对象,绕过String Pool的管辖。而如果使用 String str = "xxx"; 则先查看String Pool 是否已经存在,存在则直接返回PermGen中的该String 对象,否则,生成新的String对象,并将它加入String Pool中。
得出结论:
尽量使用String str = "abc",而不是String str = new String("abc");
用new的方法肯定会开辟新的heap空间,而前者的方法,则会通过string interning优化。
对于intern()这个String 方法好奇的同学,我推荐如下链接
Busting java.lang.String.intern() Myths
讲述了一些对intern方法调用的误解。从下面的评论上看,博主不一定全部正确,但这篇文章确实可以加深对inter机制的认识。
参考资料