Skip to content

StringTable 垃圾回收/G1 去重

StringTable 的垃圾回收

StringTable 和普通对象一样,会被 GC 回收。当一个字符串常量不再被任何地方引用时,它就可以被 GC 回收。

StringTable 的 GC 时机

StringTable 的 GC 发生在Full GC时(JDK 8 及之前)或Metaspace GC时(JDK 9+)。

bash
# JDK 8:Full GC 时清理 StringTable
# JDK 9+:G1 GC 时清理 StringTable(JDK 12+ 专门优化)

# 开启 StringTable GC 日志
java -Xlog:stringtable*=trace MyApp

StringTable GC 的日志

# JDK 8 Full GC 日志:
2026-03-22T10:00:00.123+0800: [Full GC (System.gc())
  [StringTable清理]: before 12000, reclaimed 1500, 姑12500 entries]

# G1 GC 日志(JDK 12+):
[StringTable幽幽清理]: Cleaning% of 8600 used entries, reclaimed 5000 entries

日志含义:before 12000 表示 GC 前有 12000 个 entry,reclaimed 1500 表示回收了 1500 个无效 entry。

StringTable GC 的原理

StringTable 是一个哈希表。GC 时,JVM 遍历 StringTable,对每个 entry 进行可达性分析:

java
// 简化逻辑:
for (String entry : stringTable) {
    if (!entry.isReferenced()) {
        // 没有任何地方引用这个字符串
        // 可以从 StringTable 中移除
        stringTable.remove(entry);
    }
}

关键点:一个字符串被回收的条件是没有任何地方引用它——不仅仅是 StringTable 内部没有重复引用,而是整个 Java 堆中都没有对它的引用。

java
public class StringTableGC {
    public static void main(String[] args) {
        String s = "temporary".intern();

        // 此时 StringTable 有 "temporary" 的引用
        // s 也持有引用

        s = null;  // s 不再引用
        // 如果 GC 时发现没有其他地方引用 "temporary"
        // "temporary" 就从 StringTable 中被回收
    }
}

影响 StringTable GC 的因素

1. StringTable 大小

StringTable 越大,能容纳的字符串越多,但 GC 时遍历时间越长。

bash
# JDK 8:StringTable 默认大小约 60013
java -XX:StringTableSize=100000 MyApp

# JDK 9+:StringTable 大小自动调整
# 但可以通过 -XX:StringTableSize 设置
java -XX:StringTableSize=1000000 MyApp

2. GC 类型

不同的 GC 收集器对 StringTable 的处理策略不同:

GCStringTable GC
Serial / ParallelFull GC 时清理
CMS并发标记阶段不清理,Full GC 时清理
G1JDK 12+ 在 cleanup 阶段并发清理
ZGC / Shenandoah通过着色指针,几乎不影响停顿时间

G1 的字符串去重(JDK 12+)

JDK 12 引入了 G1 的字符串去重(String Deduplication) 功能。

什么是字符串去重

字符串去重和 StringTable 的概念不同:

概念位置机制目的
StringTableStringTable 哈希表字面量共享节省内存
String Deduplication堆中的字符串对象G1 识别并合并相同内容节省更多内存

去重的原理

G1 的字符串去重会自动发现内容相同的字符串,让它们共享同一个 char[] / byte[]

去重前(两个不同的 String 对象持有各自的 char[]):
String s1 = new String(new char[]{'h','e','l','l','o'});
String s2 = new String(new char[]{'h','e','l','l','o'});

      │ G1 Deduplication 发现内容相同

去重后(两个 String 对象共享同一个 char[]):
┌──────────────┐     ┌──────────────┐
│ String s1   │     │ String s2   │
│  value ──────────────── value    │
│  (引用共享) │     │  (引用共享)  │
└──────────────┘     └──────────────┘


        char[] {'h','e','l','l','o'}
        (只有一个 char[] 数组)

启用字符串去重

bash
# JDK 12+:启用字符串去重
java -XX:+UseG1GC \
     -XX:+UseStringDeduplication \
     MyApp

# 查看去重统计
java -XX:+UseG1GC \
     -XX:+UseStringDeduplication \
     -Xlog:stringdedup*=trace \
     MyApp

去重与 intern() 的区别

维度String.intern()G1 String Deduplication
触发方式手动调用 .intern()G1 自动识别并合并
对象只处理 String处理堆中所有 String
共享内容整个 String 对象String 内部的 char[]/byte[]
版本所有 JDKJDK 12+ G1
内存节省节省 String 对象本身节省 char[] 数组

去重的日志

[String Deduplication]
[Phase1: String referencing, 10.000ms]
[Phase2: Table scanning, 15.000ms]
[Concurrent String deduplication, 8000 strings candidates,
 2000 strings deduplicated (50000 bytes saved)]

实战:如何减少 StringTable 内存

1. 避免不必要的 intern()

java
// ❌ 不好:随机生成的字符串也 intern
String key = UUID.randomUUID().toString().intern();

// ✅ 好:只 intern 固定字符串
private static final String TYPE_USER = "user";
private static final String TYPE_ADMIN = "admin";

2. 合理设置 StringTable 大小

bash
# 如果应用有大量字符串常量但 StringTable 过小
# 可能导致频繁的哈希冲突和 GC
java -XX:StringTableSize=2000000 MyApp

3. 使用 G1 字符串去重

bash
# JDK 12+ 且使用 G1GC
java -XX:+UseG1GC -XX:+UseStringDeduplication MyApp

4. FullGC 触发 StringTable GC

java
public class ForceStringTableGC {
    public static void main(String[] args) {
        // 通过 System.gc() 间接触发 Full GC,从而清理 StringTable
        // 但这不推荐,因为 Full GC 停顿时间可能很长
        System.gc();
    }
}

本节小结

StringTable 垃圾回收与 G1 去重的核心要点:

关键点说明
GC 时机Full GC(JDK 8-)/ G1 cleanup(JDK 12+)
回收条件没有任何地方引用该字符串
字符串去重JDK 12+ G1 的优化,合并内容相同的 String 的内部数组
与 intern() 的区别intern() 是手动去重 StringTable,dedup 是自动合并堆中 String
调优参数-XX:StringTableSize-XX:+UseStringDeduplication

理解 StringTable 的 GC 机制,有助于在生产环境中诊断字符串相关的内存问题。

下一节,我们来看 new String() 创建对象数面试题,这是 String 相关最经典的面试题。

基于 VitePress 构建