EN中文

深入理解Java String.intern() 内存模型

Java中string.intern()方法调用会先去字符串常量池中查找相应的字符串,如果字符串不存在,就会在字符串常量池中创建该字符串然后再返回

  • Ryan
  • 阅读 2 分钟
/images/posts/java-string-pool.png

大家知道,Java中string.intern()方法调用会先去字符串常量池中查找相应的字符串,如果字符串不存在,就会在字符串常量池中创建该字符串然后再返回。

字符串常量池是一个固定大小的HashMap,桶的数量默认是1009, 从Java7u40开始,该默认值增大到60013。在Java6当中,字符串常量池是放在Perm空间的,从Java7开始,字符串常量池被移到Heap空间。下面,我们通过测试程序来窥探字符串常量池在Java6,Java7两个不同版本底下的内存分配情况。

#测试程序

public class StringPoolTest {

    public void testStringPoolWithLongString(){
        long i=0;
        while(true){
            String longString = "This is a very long string, very very long string to test the gc behavior of the string constant pool"+i;
            longString.intern();
            i++;
        }
    }

    public static void main(String[] args){
        StringPoolTest stringPoolTest = new StringPoolTest();
        stringPoolTest.testStringPoolWithLongString();
    }
}

测试程序很简单,一个死循环,循环里面通过递增变量i制造唯一的字符串,然后用main函数启动程序。

#Java 6 我们使用版本Jdk1.6.0_29来跑该程序,打开Java VisualVM监控,可以看到,Perm区不断发生GC,由此的出结论,虽然字符串常量池放在Perm空间,但当Perm空间接近满的时候,JVM会将字符串常量池中的无用字符串回收掉。

#Java 7 下面,我们切换到Jdk1.7.0_67重跑该程序,可以看到Perm区内存分配曲线很平滑,没有出现内存分配的现象。

但在Heap空间,新的对象不断产生,然后不断触发GC

#结论 由于Perm区大小是有限的,通常只有几十MB,所以不推荐在Java6下广泛使用String.intern(),这篇文章string-intern-in-java-6-7-8的性能测试表明,在Java6底下大量使用intern()会导致应用性能的显著下降,还有可能产生OOM错误。但从Java7开始,字符串常量池被移到了Heap空间,Heap空间的大小只受制于机器的真实内存大小,因此,在Java7下使用String.intern()能更有效地减少重复String对象对内存的占用。

Frequently Asked Questions

String.intern() 的作用是什么?
String.intern() 会在字符串常量池中查找该字符串。如果已存在相等的字符串,则返回池中引用;否则将该字符串加入池中并返回引用。
生产环境应该用 String.intern() 吗?
Java 7+ 下常量池在堆中,intern() 相对安全。但大量使用仍会因池竞争导致性能问题。大多数场景下,用 HashMapConcurrentHashMap 做字符串去重是更好的选择。
为什么 Java 6 和 Java 7 的 intern() 表现不同?
Java 6 的字符串常量池在 PermGen 中(通常只有几十 MB),大量 intern() 容易导致 OutOfMemoryError。从 Java 7 开始,常量池被移到中,空间大得多,仅受物理内存限制。

Written by : Ryan

Writing about distributed systems, AI engineering, and production internals.

Recommended for You

hashCode()的实现细节

hashCode()的实现细节

本文将深入分析Java hashCode()方法的实现原理,从类加载到哈希冲突处理。

Java 中以0开头的int变量的真实值

Java 中以0开头的int变量的真实值

在Java中,以0开头的int字面量会被解释为八进制数。本文详解Java八进制整数的表达方式。