Skip to content

intern() 方法(原理/面试题/练习)

intern() 的核心语义

String.intern() 是 JVM 提供的一个 native 方法,它的作用是:将字符串加入 StringTable,并返回 StringTable 中的标准引用

这个方法看似简单,但背后藏着 JDK 6 和 JDK 8 之间最著名的行为差异。

intern() 的工作原理

java
public class Intern原理 {
    public static void main(String[] args) {
        // intern() 的执行逻辑:
        String s = new String("hello");
        String interned = s.intern();

        // intern() 检查 StringTable
        // 如果 "hello" 已在 StringTable 中 → 返回 StringTable 中的引用
        // 如果不在 → 把当前字符串加入 StringTable,返回 StringTable 中的引用
    }
}

intern() 的三种情况

调用 intern() 时:


检查 StringTable

      ├── "hello" 已在 StringTable 中
      │         │
      │         └── 返回 StringTable 中已有的引用

      └── "hello" 不在 StringTable 中

                └── JDK 6: 复制 "hello" 到 PermGen 的 StringTable,返回复制后的引用
                └── JDK 7+: 把对 "hello" 的引用添加到 StringTable,返回该引用

核心区别

版本不存在时的行为内存位置
JDK 6复制字符串到 PermGen StringTablePermGen(堆内)
JDK 7+把字符串引用添加到 StringTable堆(StringTable 移入堆)

JDK 6 vs JDK 8 的经典面试题

题目一

java
public class InternInterview1 {
    public static void main(String[] args) {
        String s1 = new StringBuilder("计算机").append("技术").toString();
        System.out.println(s1 == s1.intern());
    }
}
JDK 6JDK 7+
falsetrue

解析

  • s1 = "计算机技术"(new StringBuilder 生成的新 String 对象)
  • s1.intern() 在 JDK 6 中把「计算机技术」复制到 PermGen StringTable,返回 PermGen 中的引用 ≠ s1
  • JDK 7+ 把「计算机技术」的引用存入 StringTable,返回的就是 s1 本身 → s1 == s1.intern() = true

题目二

java
public class InternInterview2 {
    public static void main(String[] args) {
        String s1 = new String("ja") + new String("va");
        System.out.println(s1 == s1.intern());
    }
}
JDK 6JDK 7+
falsetrue

解析:同上。"java" 是 new StringBuilder 拼接的结果,intern() 后在 JDK 7+ 中返回的就是 s1 本身。

题目三

java
public class InternInterview3 {
    public static void main(String[] args) {
        String s1 = "abc";
        String s2 = new String("abc");
        System.out.println(s1 == s2);              // false
        System.out.println(s1 == s2.intern());      // true
    }
}

解析

  • "abc" 字面量在编译时就进入了 StringTable
  • s1 指向 StringTable 中的对象
  • new String("abc") 在堆中创建新 String 对象,s2 指向它
  • s2.intern() 返回 StringTable 中的标准引用,与 s1 相同

intern() 的使用场景

场景一:减少字符串对象数量

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

        for (int i = 0; i < 1_000_000; i++) {
            String field = readFromNetwork();  // 返回大量重复的字符串
            // 不 intern:1_000_000 个 String 对象
            // intern:只有 StringTable 中有 "status" 等几个字符串
            fields[i] = field.intern();
        }
        // fields 数组中的字符串共享 StringTable 中的引用
        // 节省了大量堆内存
    }
}

场景二:保证字符串唯一性

java
public class InternUnique {
    private static final String POOL_USER = "user";
    private static final String POOL_ADMIN = "admin";

    public static void main(String[] args) {
        // 使用 intern 保证全局只有一份
        String input = "user";
        boolean isUser = input == POOL_USER;
        boolean isAdmin = input == POOL_ADMIN;
    }
}

场景三:类加载器隔离的字符串比较

在需要区分不同类加载器加载的同名类时,可以用 String.intern() 保证类名字符串的一致性:

java
public class ClassNameComparison {
    public static void main(String[] args) {
        ClassLoader loader1 = new CustomLoader();
        ClassLoader loader2 = new CustomLoader();

        String name1 = loader1.loadClass("com.example.MyClass").getName();
        String name2 = loader2.loadClass("com.example.MyClass").getName();

        // 即使两个类由不同加载器加载,类名相同
        // intern 后可以比较字符串
        System.out.println(name1.intern() == name2.intern());  // true
    }
}

intern() 的代价

intern() 不是免费的午餐:

代价说明
查询开销每次调用都要在 StringTable 中查找
StringTable 膨胀大量 intern 会撑大 StringTable
PermGen OOM(JDK 6)intern 的字符串进入 PermGen,容易 OOM
启动变慢StringTable 越大,查找越慢

过度 intern 的问题

java
public class InternOveruse {
    public static void main(String[] args) {
        // 模拟过度 intern
        for (int i = 0; i < 10_000_000; i++) {
            // ❌ 不要 intern 随机生成的字符串
            String random = UUID.randomUUID().toString();
            random.intern();  // UUID 数量无限,StringTable 会爆炸
        }
    }
}

正确做法:只 intern 少量固定的字符串(如配置值、枚举值)。

intern() 与 JIT 编译器

JIT 编译器对 intern() 做了特殊优化:

  • 如果 JIT 确认某个字符串一定会调用 intern(),可能会在编译时就直接指向 StringTable 中的对象
  • 这种优化叫 String Constant Folding
java
public class InternJIT {
    public static void main(String[] args) {
        String s = new String("hello").intern();
        // JIT 可能优化成 String s = "hello";
        // 因为 "hello" 字面量已经在 StringTable 中
    }
}

本节小结

intern() 方法的核心要点:

关键点说明
作用把字符串加入 StringTable,返回 StringTable 中的引用
JDK 6 行为复制字符串到 PermGen StringTable
JDK 7+ 行为把字符串引用添加到堆中的 StringTable
使用场景减少重复字符串、保证字符串唯一性
注意事项不要过度 intern,随机字符串不要 intern

理解 intern() 的关键是记住:JDK 6 和 JDK 7+ 的区别是复制 vs 引用

下一节,我们来看 StringTable 垃圾回收/G1 去重

基于 VitePress 构建