StringTable 位置调整与验证
StringTable:字符串常量的哈希表
StringTable(字符串常量池)是运行时常量池中最重要的组成部分之一。它是一个哈希表,存储了所有的字符串常量,用于字符串的快速查找和去重。
JDK 7 之前,StringTable 在 PermGen 中;JDK 7 移入堆;JDK 8 之后,它仍然在堆中。
StringTable 的位置历史
| JDK 版本 | StringTable 位置 | 说明 |
|---|---|---|
| JDK 6 | PermGen(堆内) | 大小受限,容易 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
}
}1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
StringTable 的特点:
- 去重:相同的字符串字面量共享同一个对象,节省内存
- 哈希查找:O(1) 的字符串查找性能
- 惰性加载:字面量在被首次引用时才加入 StringTable
StringTable 大小调优
StringTable 的大小直接影响字符串驻留的性能:
bash
# 设置 StringTable 大小(JDK 8)
# 默认值大约 60013(可自动调整)
java -XX:StringTableSize=100000 MyApp
# 查看 StringTable 统计信息(JDK 8)
jstat -gccapacity <pid>
# 输出中可以看到 StringTable 相关统计1
2
3
4
5
6
7
2
3
4
5
6
7
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
}
}1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
实战场景:节省内存
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();
}
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
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();
}
}1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
查看 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 MyApp1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
StringTable 相关的 JNI 调优
如果应用中有大量的 String.hashCode() 调用,StringTable 的性能会影响整体表现:
bash
# JDK 8 中 StringTable 的哈希种子
java -XX:StringTableSize=1000000 MyApp
# 可以减少哈希冲突,提高查找性能1
2
3
2
3
StringTable vs StringBuilder vs StringBuffer
虽然和 StringTable 不是同一概念,但容易混淆:
| 类/概念 | 位置 | 线程安全 | 说明 |
|---|---|---|---|
String | 堆 + StringTable | 不可变 | 字符串对象 |
StringBuilder | 堆 | 不安全 | 可变字符串,高效拼接 |
StringBuffer | 堆 | 安全 | 可变字符串,线程安全 |
StringTable | 堆(String 的内部结构) | - | 字符串常量池,哈希表 |
本节小结
StringTable 的核心要点:
| 关键点 | 说明 |
|---|---|
| 位置 | JDK 7+ 在堆中,JDK 6 在 PermGen |
| 去重机制 | 相同字符串字面量共享同一个对象 |
| 大小调优 | -XX:StringTableSize=N |
| intern() | 手动将字符串加入 StringTable |
| GC | StringTable 中的常量也会被 GC 回收 |
理解 StringTable,有助于理解字符串的内存模型和 intern() 的实际效果。
下一节,我们来看 方法区垃圾回收,理解方法区何时以及如何被 GC 回收。
