运行时常量池不负责字符串动态入池,真正执行该操作的是字符串常量池(StringTable);字面量入池发生于类加载解析阶段,属静态行为,仅 intern() 调用才实现运行时动态入池。

运行时常量池本身不负责字符串的“动态入池”,真正执行这个动作的是字符串常量池(StringTable),而它和运行时常量池是两个独立但有协作关系的结构。所谓“字符串字面量变量动态入池”,其实是个常见误解——字面量的入池发生在类加载阶段,是静态确定的;只有通过 intern() 显式调用,才可能在运行时把堆中字符串“尝试”加入字符串常量池。
字面量入池:编译期决定,类加载时完成
当你写 String s = "hello",这个 "hello" 是编译期就确定的字面量,会被 javac 收集进类文件的常量池(class constant pool)。JVM 在类加载的解析阶段(resolve phase),遇到 ldc 指令时,会触发以下流程:
- 计算该字面量内容的哈希值,定位到字符串常量池(StringTable)对应桶位
- 遍历桶内链表或红黑树,用
equals()逐个比对字符内容(不是只看哈希) - 命中则直接返回已有对象引用;未命中则在堆中新建
String对象,并将该对象的引用存入 StringTable
整个过程在类首次加载时批量完成,属于“静态入池”,不是运行时动态行为。
运行时“动态入池”:仅靠 intern() 显式触发
运行时生成的字符串(比如 new StringBuilder().append("he").append("llo").toString() 或 new String("hello"))默认不会进字符串常量池。只有显式调用 .intern(),才会尝试将其纳入管理:
- JDK 1.7+ 中,
intern()会先查字符串常量池:若已存在相同内容的引用,直接返回那个引用 - 若不存在,则把当前字符串对象的引用存入 StringTable,并返回该引用
- 注意:它不会复制字符串内容,也不改变原对象;只是建立或复用一个指向堆中某个
String实例的引用
运行时常量池和字符串常量池的关系
二者物理上分离,职责不同:
- 运行时常量池:位于元空间(JDK 1.8+),是每个类或接口的常量池在 JVM 中的运行时表示,存储类/方法/字段符号引用、数字字面量、尚未解析的字符串字面量索引等
- 字符串常量池(StringTable):位于堆内存,是一个全局共享的哈希表(默认大小 60013),只存字符串内容对应的堆对象引用
- 当运行时常量池里的字符串字面量第一次被
ldc指令使用时,才触发向 StringTable 的查找与可能的入库动作
容易混淆的点:拼接字符串是否“动态入池”?
要看拼接是否在编译期可确定:
-
"ab" + "c"→ 编译器优化为"abc",走字面量流程,进池 -
"ab" + s(s 是变量)→ 运行时拼接,结果是堆中新对象,不进池,除非调用intern() -
new String("abc").intern()→ 先建堆对象,再查池;若池中无,则把该堆对象引用存入并返回

















