StringTable性能调优

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();
}

再次查看运行结束后的内存占用:

StringTable性能调优

可见这次String和char[]占用的内存接近100M,相比减少很多。

所以当java程序可能引用很多相同内容的字符串的时候,最好将字符串入池,能有效减轻内存负担,所增开销仅是intern方法内部开销。

原创文章,作者:彭晨涛,如若转载,请注明出处:https://www.codetool.top/article/stringtable%e6%80%a7%e8%83%bd%e8%b0%83%e4%bc%98/