StringTable是jvm运行时常量池表中的字符串常量池,深入理解java虚拟机第三版读书笔记02有相关的介绍。
无论是运行时常量表还是StringTable,他们内部都是通过哈希表来保证字符串常量的唯一性,可以通过配置jvm参数-XX:+PrintStringTableStatistics
来查看常量表的详细信息。
验证StringTable的垃圾回收
首先我们运行下面这段代码:
使用虚拟机参数:-XX:+PrintGCDetails -XX:+PrintStringTableStatistics
public static void main(String[] args) throws IOException {
for(int i = 0;i<10000;i++){
String.valueOf(i).intern();
}
}
截取部分输出:
StringTable statistics:
Number of buckets : 60013 = 480104 bytes, avg 8.000
Number of entries : 11695 = 280680 bytes, avg 24.000
Number of literals : 11695 = 635176 bytes, avg 54.312
可见StringTable总共有60013个桶,10000个字符串全部进入常量池,且没有垃圾回收的信息输出。
那我们让进入堆的内存适度减小,让它有可能发生堆空间不足的情况:
使用虚拟机参数:-Xmx10m -XX:+PrintGCDetails -XX:+PrintStringTableStatistics
再次运行上面那段代码
截取部分输出:
[GC (Allocation Failure) [PSYoungGen: 2048K->504K(2560K)] 2048K->720K(9728K), 0.0014597 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
Number of buckets : 60013 = 480104 bytes, avg 8.000
Number of entries : 7981 = 191544 bytes, avg 24.000
Number of literals : 7981 = 456584 bytes, avg 57.209
可以看到,这次10000个字符串并没有全部进入常量池,一部分被回收掉了。
调整桶的个数
我们都知道哈希表的存取效率和桶的个数是紧密相关的,桶的个数太大则空间有可能浪费,太小则可能效率过低。
我们运行这段代码:
使用虚拟机参数-XX:+PrintStringTableStatistics
public static void main(String[] args) throws IOException {
long start = System.nanoTime();
List<String> list = new ArrayList<>();
for(int i = 0;i<500000;i++){
list.add(String.valueOf(i).intern());
}
System.out.println("cost:"+(System.nanoTime()-start)/1000000000.0 + "s");
}
截取部分输出:
cost:0.0997132s
StringTable statistics:
Number of buckets : 60013 = 480104 bytes, avg 8.000
Number of entries : 501704 = 12040896 bytes, avg 24.000
Number of literals : 501704 = 28075720 bytes, avg 55.961
我们使用虚拟机参数来调整桶的个数:
使用虚拟机参数-XX:StringTableSize=1009 -XX:+PrintStringTableStatistics
cost:3.1234771s
Number of buckets : 1009 = 8072 bytes, avg 8.000
Number of entries : 503097 = 12074328 bytes, avg 24.000
Number of literals : 503097 = 28176912 bytes, avg 56.007
可见消耗时间明显增大
我们再将桶的个数调至更大:
使用虚拟机参数-XX:StringTableSize=1009 -XX:+PrintStringTableStatistics
截取部分输出:
cost:0.0879392s
StringTable statistics:
Number of buckets : 1000000 = 8000000 bytes, avg 8.000
Number of entries : 501704 = 12040896 bytes, avg 24.000
Number of literals : 501704 = 28075720 bytes, avg 55.961
可见消耗时间有所减少
是否该使用intern方法将字符串入池
我们运行下面这段代码,使用jvisualvm查看堆内存的变化情况:
public static void main(String[] args) throws IOException {
System.in.read();
List<String> list = new ArrayList<>();
for(int n = 0;n<10;n++){//每个字符串搞出10次
for(int i = 0;i<500000;i++){
list.add(String.valueOf(i));
}
System.out.println("part "+n+" has done");
}
System.in.read();
}


可见String和char[]占用了极高内存(接近300M)
我们在原来代码的基础上使用intern方法:
public static void main(String[] args) throws IOException {
System.in.read();
List<String> list = new ArrayList<>();
for(int n = 0;n<10;n++){//每个字符串搞出10次
for(int i = 0;i<500000;i++){
list.add(String.valueOf(i).intern());
}
System.out.println("part "+n+" has done");
}
System.in.read();
}
再次查看运行结束后的内存占用:

可见这次String和char[]占用的内存接近100M,相比减少很多。
所以当java程序可能引用很多相同内容的字符串的时候,最好将字符串入池,能有效减轻内存负担,所增开销仅是intern方法内部开销。
原创文章,作者:彭晨涛,如若转载,请注明出处:https://www.codetool.top/article/stringtable%e6%80%a7%e8%83%bd%e8%b0%83%e4%bc%98/