Java 字符串常量池介绍,String Pool 的实现( 二 )

统计数据中包含了 buckets 的数量,总的 String 对象的数量,占用的总空间,单个 bucket 的链表平均长度和最大长度等 。
上面的数据是在 Java 8 的环境中打印出来的,Java 7 的信息稍微少一些,主要是没有 footprint 的数据:
StringTable statistics:Number of buckets:60003Average bucket size:67Variance of bucket size :20Std. dev. of bucket size:4Maximum bucket size:84测试 String Pool 的性能接下来,我们来跑个测试,测试下 String Pool 的性能问题,并讨论 -XX:StringTableSize=N 参数的作用 。
我们将使用 String#intern() 往字符串常量池中添加 400万 个不同的长字符串 。
package com.javadoop;import java.lang.ref.WeakReference;import java.util.ArrayList;import java.util.List;import java.util.WeakHashMap;public class StringTest {public static void main(String[] args) {test(4000000);}private static void test(int cnt) {final List<String> lst = new ArrayList<String>(1024);long start = System.currentTimeMillis();for (int i = 0; i < cnt; ++i) {final String str = "Very very very very very very very very very very very very very very " +"very long string: " + i;lst.add(str.intern());if (i % 200000 == 0) {System.out.println(i + 200000 + "; time = " + (System.currentTimeMillis() - start) / 1000.0 + " sec");start = System.currentTimeMillis();}}System.out.println("Total length = " + lst.size());}}我们每插入 20万 条数据,输出一次耗时 。
# 编译javac -d . StringTest.java# 使用默认 table size (60013) 运行一次java -Xms2g -Xmx2g com.javadoop.StringTest# 设置 table size 为 400031,再运行一次java -Xms2g -Xmx2g -XX:StringTableSize=400031 com.javadoop.StringTest

Java 字符串常量池介绍,String Pool 的实现

文章插图
 
从左右两部分数据可以很直观看出来,插入的性能主要取决于链表的平均长度 。当链表平均长度为 10 的时候,我们看到性能是几乎没有任何损失的 。
还是那句话,根据自己的实际情况,考虑是否要设置 -XX:StringTableSize=N,还是使用默认值 。
讨论自建 String Pool这一节我们来看下自己使用 HashMap 来实现 String Pool 。
这里我们需要使用 WeakReference:
private static final WeakHashMap<String, WeakReference<String>> pool= new WeakHashMap<String, WeakReference<String>>(1024);private static String manualIntern(final String str) {final WeakReference<String> cached = pool.get(str);if (cached != null) {final String value = https://www.isolves.com/it/cxkf/yy/JAVA/2019-12-23/cached.get();if (value != null) {return value;}}pool.put(str, new WeakReference(str));return str;}我们使用 1000 * 1000 * 1000 作为入参 cnt 的值进行测试,分别测试 [1] 和 [2]:
private static void test(int cnt) {final List<String> lst = new ArrayList<String>(1024);long start = System.currentTimeMillis();for (int i = 0; i < cnt; ++i) {// [1]lst.add(String.valueOf(i).intern());// [2]// lst.add(manualIntern(String.valueOf(i)));if (i % 200000 == 0) {System.out.println(i + 200000 + "; time = " + (System.currentTimeMillis() - start) / 1000.0 + " sec");start = System.currentTimeMillis();}}System.out.println("Total length = " + lst.size());}测试结果,2G 的堆大小,如果使用 String#intern(),大概在插入 3000万 数据的时候,开始进入大量的 FullGC 。
而使用自己写的 manualIntern(),大概到 1400万 的时候,就已经不行了 。
没什么结论,如果要说点什么的话,那就是不要自建 String Pool,没必要 。
小结记住有两个 JVM 参数可以设置:-XX:StringTableSize=N、-XX:+PrintStringTableStatistics
StringTableSize,在 Java 6 中,是 1009;在 Java 7 和 Java 8 中,默认都是 60013,如果有必要请自行扩大这个值 。

【Java 字符串常量池介绍,String Pool 的实现】


推荐阅读