运行时常量池vs字符串常量池

  • 运行时常量池(Runtime Constant Pool):类加载后生成,属于类级别的常量池
  • 字符串常量池(String Pool / StringTable):专门存储字符串的全局共享池

可以理解为:

字符串常量池是运行时常量池的一部分演化出来的“特殊优化区”

一、运行时常量池

1.1 定义

运行时常量池是 .class 文件中 常量池(Constant Pool) 在运行时的表现形式。

1.2 存储内容

包括:

  • 字面量(Literal)
    • 数值(int、float…)
    • 字符串(早期直接在这里)
  • 符号引用(Symbolic Reference)
    • 类名
    • 方法名
    • 字段名

1.3 特点

  • 每个类都有一个
  • 在类加载时创建
  • 支持动态添加(比如 String.intern()

1.4 存储位置(重点)

  • JDK7 之前:方法区(Method Area / PermGen)
  • JDK8 之后:方法区实现为 元空间(Metaspace)

二、字符串常量池(String Pool)

2.1 定义

专门用于存储 字符串对象引用 的池

2.2 特点

  • 全局唯一(所有类共享)
  • 用于字符串复用(避免重复创建)
  • 本质是一个 HashTable(StringTable)

2.3 存储位置(重点)

  • JDK6:方法区(PermGen)
  • JDK7+:堆(Heap) (面试重点)

这也是你看到“静态变量/字符串在堆中”的原因之一

三、关键区别对比

维度 运行时常量池 字符串常量池
范围 每个类独立 全局共享
内容 各种常量 + 符号引用 只存字符串
位置(JDK8) 元空间
创建时机 类加载时 按需创建
是否唯一
是否可扩展 ✅(运行时) ✅(intern)

四、两者关系(重点理解)

4.1 编译期

1
String s = "abc";
  • "abc" → 放入 class 常量池
  • 类加载 → 进入 运行时常量池
  • 同时 "abc" → 进入 字符串常量池

4.2 运行期

1
String s1 = new String("abc");

过程:

  1. "abc" 已存在于字符串常量池
  2. new String() → 在堆中创建新对象
  3. s1 指向堆对象

4.3 intern() 行为

1
String s2 = new String("abc").intern();
  • 如果字符串池中不存在 → 放进去
  • 返回池中的引用

五、经典面试题(必须会)

❓1

1
2
3
String s1 = "abc";
String s2 = "abc";
System.out.println(s1 == s2);

✅ true
👉 因为都指向字符串常量池同一个对象

❓2

1
2
3
String s1 = new String("abc");
String s2 = "abc";
System.out.println(s1 == s2);

❌ false
👉 一个在堆,一个在字符串池

❓3(高频)

1
2
3
4
5
6
String s1 = new String("a") + new String("b");
String s2 = s1.intern();
String s3 = "ab";

System.out.println(s1 == s2);
System.out.println(s2 == s3);

👉 JDK7+ 结果:

1
2
true
true

原因:

  • "ab" 首次出现
  • intern() 直接把堆中对象放进字符串池

六、总结(面试模板)

可以这样回答:

运行时常量池是类加载后生成的常量池,属于每个类独立的数据结构,存储字面量和符号引用;而字符串常量池是 JVM 专门优化字符串复用设计的全局共享池,只存储字符串对象引用。在 JDK7 之后,字符串常量池从方法区迁移到了堆中,两者在实现和作用上是不同层次的结构。