前言
字符串常量池在java6之前位于永久代,永久代从名字上已经告诉我们这里垃圾回收效果很差,如果字符串常量池中含有大量的字符串,那么很容易造成永久代溢出。从java7开始,字符串常量池挪到了堆中,堆空间一般比较大,而且堆空间的回收效率很高,所以相对于在永久代,放到堆空间后,内存溢出的情况大大减少。
本文接下来先通过代码观察一下常量池,然后介绍字符串常量池如何实现的。本文使用的是jdk8。
本文目录
- 一、代码实验
- 二、字符串常量池实现原理
一、代码实验
这里主要使用String.intern方法,intern()方法的作用是如果字符串不在常量池中,则将该字符串的引用放入常量池,并将引用返回,如果在常量池中,则将常量池中字符串的引用返回。
public static void main(String[] args){
String s1=new String("hello");[1]
String s2=s1.intern();[2]
System.out.println(s1==s2);//false
System.out.println(s1=="hello");//false
System.out.println(s2=="hello");//true
String s3=new StringBuilder().append(s1).append(s2).toString();[3]
String s4=s3.intern();[4]
System.out.println(s3==s4);//true
System.out.println(s3=="hellohello");//true
}
运行结果:
false
false
true
true
true
代码[1]处因为hello是一个字面常量,所以java会将字符串hello放入常量池一份,同时在堆中创建一个String对象,这两个对象是不同的,而且字符串常量池中已经有了字符串hello,所以代码[2]处直接返回常量池的引用,因此也就出现了下面打印的false、false、true。
代码[3]处会在堆中创建一个字符串对象hellohello,该字符串此时在常量池中不存在,然后代码[4]处将堆中的引用放入到常量池中,并返回该引用,因此s3等于s4。接下来的字面常量hellohello其实就是代码[4]放入字符串常量池中的引用,因此执行也是true。
下面我写了一段代码来验证字符串常量池挪到了堆区中。
public static void main(String[] args)throws Exception{
String name = ManagementFactory.getRuntimeMXBean().getName();
System.out.println(name);
String pid = name.split("@")[0];
System.out.println("Pid is:" + pid); //打印PID
Thread.sleep(2000); //为了更好的观察现象
//s1和s2都是很长的字符串
String s1 = "qqq。。。";
String s2 = "qqqw。。。。";
System.out.print(s1.length());
System.gc();
s1 = null;
s2 = null;
Thread.sleep(2000); //为了更好的观察现象
System.gc();
Thread.sleep(2000); //为了更好的观察现象
System.gc();
Thread.sleep(2000); //为了更好的观察现象
}
下面是运行jstat命令的结果:
PS C:\Program Files\Java\jdk1.8.0_161\bin> .\jstat.exe -gc 16524 1s
S0C S1C S0U S1U EC EU OC OU MC MU CCSC CCSU YGC YGCT FGC FGCT GCT
13312.0 13312.0 0.0 0.0 82432.0 11578.1 218624.0 0.0 4480.0 781.6 384.0 75.9 0 0.000 0 0.000 0.000
13312.0 13312.0 0.0 0.0 82432.0 1648.7 218624.0 1872.3 4864.0 3635.7 512.0 382.9 1 0.002 1 0.009 0.011
13312.0 13312.0 0.0 0.0 82432.0 1648.7 218624.0 1512.4 4864.0 3641.2 512.0 382.9 2 0.003 2 0.022 0.025
13312.0 13312.0 0.0 0.0 82432.0 0.0 218624.0 1508.7 4864.0 3641.3 512.0 382.9 3 0.004 3 0.042 0.046
一共进行了三次垃圾回收,从结果上可以很明显的看出,老年代(OU)和新生代(EU)的总大小在减少。上面的结果可以说明字符串常量池中的字符串和普通对象一样,初始位于新生代,随着代数的增加,会被挪到老年代中。
二、字符串常量池实现原理
字符串常量池使用哈希表来存储字符串,类似于HashMap,初始桶的个数是60013(可以使用-XX:+PrintStringTableStatistics在程序退出时打印结果可以看到桶的个数),如果存储的字符串非常多,可以使用-XX:StringTableSize增大桶个数,以减少冲突。
参考文章
https://blog.csdn.net/weixin_38308374/article/details/110674739