历史
在JDK1.7之前运行时常量池逻辑包含字符串常量池存放在方法区, 此时hotspot虚拟机对方法区的实现为永久代
在JDK1.7 字符串常量池被从方法区拿到了堆中, 这里没有提到运行时常量池,也就是说字符串常量池被单独拿到堆,运行时常量池剩下的东西还在方法区, 也就是hotspot中的永久代
在JDK1.8 hotspot移除了永久代用元空间(Metaspace)取而代之, 这时候字符串常量池还在堆, 运行时常量池还在方法区, 只不过方法区的实现从永久代变成了元空间(Metaspace)
字符串常量池变动原因
字符串存在永久代中,现实使用中易出问题, 由于永久代内存经常不够用或发生内存泄露,爆出异常
java.lang.OutOfMemoryError: PermGen
类及方法的信息等比较难确定其大小,因此对于永久代的大小指定比较困难,太小容易出现永久代溢出,太大则容易导致老年代溢出。
永久代会为 GC 带来不必要的复杂度,并且回收效率偏低。
常量池
即class文件常量池,是class文件的一部分,用于保存编译时确定的数据。
字面量
int i = 1;把整数1赋值给int型变量i,整数1就是Java字面量,
String s = "abc";中的abc也是字面量。
符号引用
符号引用以一组符号来描述所引用的目标, 符号可以是任何形式的字面量, 只要使用时能够无歧义的定位到目标即可.例如,在Class文件中它以CONSTANT_Class_info、CONSTANT_Fieldref_info、CONSTANT_Methodref_info等类型的常量出现。比如org.simple.People类引用了org.simple.Language类,在编译时People类并不知道Language类的实际内存地址,因此只能使用符号org.simple.Language(实际中是由类似于CONSTANT_Class_info的常量来表示的)来表示Language类的地址。
运行时常量池
- 运行时常量池相比于常量池的不同特征在于运行时常量池具备动态性,也就是说不一定要编译期的时候才能产生常量,也可以在运行时产生常量,比如在运行期间调用String.intern()方法,也可以将新的常量放入运行时常量池中。
- 运行时常量池是在类加载完成之后,将每个class文件常量池中的符号引用值转存到运行时常量池中,也就是说,每个class都有一个运行时常量池,类在解析之后,将符号引用替换成直接引用,与全局常量池中的引用值保持一致。
由于字符串结合1的特性导致,jdk6时期的永久代爆出
java.lang.OutOfMemoryError: PermGen
因此出现了字符串常量池。
字符串常量池
字符串常量池与运行时常量池不同,运行时常量池是类独有的;而字符串常量池是全局共享的。
在HotSpot VM里实现的string pool功能的是一个StringTable类,它是一个哈希表,里面存的是驻留字符串(也就是我们常说的用双引号括起来的)的引用(而不是驻留字符串实例本身),也就是说在堆中的某些字符串实例被这个StringTable引用之后就等同被赋予了”驻留字符串”的身份。这个StringTable在每个HotSpot VM的实例只有一份,被所有的类共享。
//在堆中创建中创建“rrr” 赋值给aa
String aa = new String("rr") + new String("r");
//在堆中创建中创建“rrr” 赋值给bb
String bb = new String("rr") + new String("r");
//在字符串常量池中找不到"rrr",因此bb作为新的常量
System.out.println(bb.intern() == bb);//true
//在字符串常量池中找到"rrr",因此指向bb相同值
String rr ="rrr";
System.out.println(bb == rr);//true
//在堆中创建“AA”,"BB"
//在堆中创建中创建“AABB” 赋值给a7
String a7 = new String("AA") + new String("BB");
//在字符串常量池中找不到,因此a7作为新的常量
a7 == a7.intern() //true
//在字符串常量池中创建“ABABCDCD” 赋值给test
String test = "ABABCDCD";
//在堆中创建“ABAB”,"CDCD"
//在堆中创建“ABABCDCD” 赋值给a8
String a8 = new String("ABAB") + new String("CDCD");
//在字符串常量池中找“ABABCDCD” 赋值给a9,与test变量同指向
String a9 = "ABAB" + "CDCD";
//a8.intern()返回的是第一个“ABABCDCD”即test
a8 == a8.intern() //false
//a8和a9不指向同一对象
a8 == a9 //false
//都指向test
a9 == a8.intern()//true