在java的内存分配中,常常听到不少关于常量池的描述,我开始看的时候也是看的很模糊,网上五花八门的说法简直太多了,最后查阅各类资料,终于算是差很少理清了,不少网上说法都有问题,笔者尝试着来区分一下这几个概念。git
1.全局字符串池(string pool也有叫作string literal pool)
全局字符串池里的内容是在类加载完成,通过验证,准备阶段以后在堆中生成字符串对象实例,而后将该字符串对象实例的引用值存到string pool中(记住:string pool中存的是引用值而不是具体的实例对象,具体的实例对象是在堆中开辟的一块空间存放的。)。 在HotSpot VM里实现的string pool功能的是一个StringTable类,它是一个哈希表,里面存的是驻留字符串(也就是咱们常说的用双引号括起来的)的引用(而不是驻留字符串实例自己),也就是说在堆中的某些字符串实例被这个StringTable引用以后就等同被赋予了”驻留字符串”的身份。这个StringTable在每一个HotSpot VM的实例只有一份,被全部的类共享。github
2.class文件常量池(class constant pool)
咱们都知道,class文件中除了包含类的版本、字段、方法、接口等描述信息外,还有一项信息就是常量池(constant pool table),用于存放编译器生成的各类字面量(Literal)和符号引用(Symbolic References)。 字面量就是咱们所说的常量概念,如文本字符串、被声明为final的常量值等。 符号引用是一组符号来描述所引用的目标,符号能够是任何形式的字面量,只要使用时能无歧义地定位到目标便可(它与直接引用区分一下,直接引用通常是指向方法区的本地指针,相对偏移量或是一个能间接定位到目标的句柄)。通常包括下面三类常量:数据结构
- 类和接口的全限定名
- 字段的名称和描述符
- 方法的名称和描述符
常量池的每一项常量都是一个表,一共有以下表所示的11种各不相同的表结构数据,这每一个表开始的第一位都是一个字节的标志位(取值1-12),表明当前这个常量属于哪一种常量类型。 每种不一样类型的常量类型具备不一样的结构,具体的结构本文就先不叙述了,本文着重区分这三个常量池的概念(读者若想深刻了解每种常量类型的数据结构能够查看《深刻理解java虚拟机》第六章的内容)。jvm
3.运行时常量池(runtime constant pool)
当java文件被编译成class文件以后,也就是会生成我上面所说的class常量池,那么运行时常量池又是何时产生的呢?函数
jvm在执行某个类的时候,必须通过加载、链接、初始化,而链接又包括验证、准备、解析三个阶段。而当类加载到内存中后,jvm就会将class常量池中的内容存放到运行时常量池中,由此可知,运行时常量池也是每一个类都有一个。在上面我也说了,class常量池中存的是字面量和符号引用,也就是说他们存的并非对象的实例,而是对象的符号引用值。而通过解析(resolve)以后,也就是把符号引用替换为直接引用,解析的过程会去查询全局字符串池,也就是咱们上面所说的StringTable,以保证运行时常量池所引用的字符串与全局字符串池中所引用的是一致的。post
举个实例来讲明一下:spa
1 2 3 4 5 6 7 8 |
String str1 = "abc"; String str2 = new String("def"); String str3 = "abc"; String str4 = str2.intern(); String str5 = "def"; System.out.println(str1 == str3);//true System.out.println(str2 == str4);//false System.out.println(str4 == str5);//true |
上面程序的首先通过编译以后,在该类的class常量池中存放一些符号引用,而后类加载以后,将class常量池中存放的符号引用转存到运行时常量池中,而后通过验证,准备阶段以后,在堆中生成驻留字符串的实例对象(也就是上例中str1所指向的”abc”实例对象),而后将这个对象的引用存到全局String Pool中,也就是StringTable中,最后在解析阶段,要把运行时常量池中的符号引用替换成直接引用,那么就直接查询StringTable,保证StringTable里的引用值与运行时常量池中的引用值一致,大概整个过程就是这样了。指针
回到上面的那个程序,如今就很容易解释整个程序的内存分配过程了,首先,在堆中会有一个”abc”实例,全局StringTable中存放着”abc”的一个引用值,而后在运行第二句的时候会生成两个实例,一个是”def”的实例对象,而且StringTable中存储一个”def”的引用值,还有一个是new出来的一个”def”的实例对象,与上面那个是不一样的实例,当在解析str3的时候查找StringTable,里面有”abc”的全局驻留字符串引用,因此str3的引用地址与以前的那个已存在的相同,str4是在运行的时候调用intern()函数,返回StringTable中”def”的引用值,若是没有就将str2的引用值添加进去,在这里,StringTable中已经有了”def”的引用值了,因此返回上面在new str2的时候添加到StringTable中的 “def”引用值,最后str5在解析的时候就也是指向存在于StringTable中的”def”的引用值,那么这样一分析以后,下面三个打印的值就容易理解了。code
总结
- 1.全局常量池在每一个VM中只有一份,存放的是字符串常量的引用值。
- 2.class常量池是在编译的时候每一个class都有的,在编译阶段,存放的是常量的符号引用。
- 3.运行时常量池是在类加载完成以后,将每一个class常量池中的符号引用值转存到运行时常量池中,也就是说,每一个class都有一个运行时常量池,类在解析以后,将符号引用替换成直接引用,与全局常量池中的引用值保持一致。