Skip to content

StringTable 位置调整与验证

StringTable:字符串常量的哈希表

StringTable(字符串常量池)是运行时常量池中最重要的组成部分之一。它是一个哈希表,存储了所有的字符串常量,用于字符串的快速查找和去重。

JDK 7 之前,StringTable 在 PermGen 中;JDK 7 移入堆;JDK 8 之后,它仍然在堆中。

StringTable 的位置历史

JDK 版本StringTable 位置说明
JDK 6PermGen(堆内)大小受限,容易 OOM
JDK 7堆(从 PermGen 移出)可以利用堆的 GC
JDK 8+无变化

StringTable 的工作原理

java
public class StringTableDemo {
    public static void main(String[] args) {
        String s1 = "hello";  // "hello" 加入 StringTable
        String s2 = "hello";  // 复用 StringTable 中的 entry

        // StringTable 中相同字符串只有一个 entry
        // 所有字面量 "hello" 指向同一个对象
        System.out.println(s1 == s2);  // true
    }
}

StringTable 的特点:

  • 去重:相同的字符串字面量共享同一个对象,节省内存
  • 哈希查找:O(1) 的字符串查找性能
  • 惰性加载:字面量在被首次引用时才加入 StringTable

StringTable 大小调优

StringTable 的大小直接影响字符串驻留的性能:

bash
# 设置 StringTable 大小(JDK 8)
# 默认值大约 60013(可自动调整)
java -XX:StringTableSize=100000 MyApp

# 查看 StringTable 统计信息(JDK 8)
jstat -gccapacity <pid>
# 输出中可以看到 StringTable 相关统计

StringTableSize 的建议

场景建议
字符串常量少默认值即可
大量字符串字面量增大 StringTableSize(如 100000~1000000)
频繁 intern()增大 StringTableSize
内存受限减小 StringTableSize(不推荐)

String.intern() 与 StringTable

String.intern() 是手动将字符串加入 StringTable 的方式:

java
public class InternStringTable {
    public static void main(String[] args) {
        // 手动 intern
        String s = new String("hello");
        String interned = s.intern();  // "hello" 加入/获取 StringTable 引用

        // intern 后的字符串 == 字面量
        System.out.println(interned == "hello");  // true
    }
}

实战场景:节省内存

java
public class SaveMemory {
    public static void main(String[] args) {
        // 场景:从网络读取大量重复的 JSON 字段
        String[] fields = new String[1000000];

        for (int i = 0; i < 1000000; i++) {
            String field = getFromNetwork();  // 返回 "status", "message" 等重复值
            // 不 intern:每个字段都是独立对象,内存占用大
            // intern:复用 StringTable 中的对象,大幅节省内存
            fields[i] = field.intern();
        }
    }
}

StringTable 与 GC

StringTable 中的字符串也受 GC 管理:

java
public class StringTableGC {
    public static void main(String[] args) {
        String s = "will be collected".intern();
        // 如果没有任何引用指向 "will be collected"
        // 这个字符串常量可能在 GC 时被回收
        s = null;
        // 调用 System.gc() 后,常量可能被回收
        System.gc();
    }
}

查看 StringTable 大小

bash
# JDK 8 及之后,可以用 heap dump 分析
jmap -dump:file=/tmp/heap.hprof <pid>

# 用 jhat 或 MAT 分析
# 搜索 java.lang.String 和 StringTable 相关结构

# JDK 11+ 可以用更详细的 GC 日志
java -Xlog:gc+stringtable=trace MyApp

StringTable 相关的 JNI 调优

如果应用中有大量的 String.hashCode() 调用,StringTable 的性能会影响整体表现:

bash
# JDK 8 中 StringTable 的哈希种子
java -XX:StringTableSize=1000000 MyApp
# 可以减少哈希冲突,提高查找性能

StringTable vs StringBuilder vs StringBuffer

虽然和 StringTable 不是同一概念,但容易混淆:

类/概念位置线程安全说明
String堆 + StringTable不可变字符串对象
StringBuilder不安全可变字符串,高效拼接
StringBuffer安全可变字符串,线程安全
StringTable堆(String 的内部结构)-字符串常量池,哈希表

本节小结

StringTable 的核心要点:

关键点说明
位置JDK 7+ 在堆中,JDK 6 在 PermGen
去重机制相同字符串字面量共享同一个对象
大小调优-XX:StringTableSize=N
intern()手动将字符串加入 StringTable
GCStringTable 中的常量也会被 GC 回收

理解 StringTable,有助于理解字符串的内存模型和 intern() 的实际效果。

下一节,我们来看 方法区垃圾回收,理解方法区何时以及如何被 GC 回收。

基于 VitePress 构建