ldc指令不直接访问字符串常量池,而是加载constant_string_info索引,首次执行时触发解析:查stringtable、无则堆中新建并驻留;new string("abc")虽含ldc,但new强制创建堆对象,与驻留无关。

ldc 指令本身不直接访问字符串常量池(StringTable),它只从运行时常量池中加载一个 CONSTANT_String_info 结构的索引;真正触发字符串驻留(即“进池”或“查池”)的是后续的符号引用解析过程,发生在类初始化前或首次执行 ldc 时。
ldc 加载的是 CONSTANT_String_info,不是 String 对象
Java 源码中写 String s = "hello";,编译后字节码通常是 ldc #5。这里的 #5 指向 class 文件常量池中一个 CONSTANT_String_info 项——但它不存字符串内容,只存另一个索引(比如 #12),而 #12 才是真正的 CONSTANT_Utf8_info,里面是 UTF-8 编码的字节序列。
也就是说:ldc 压栈的只是一个“待解析的符号引用”,不是 java.lang.String 实例,更不是对 StringTable 的直接引用。
- 未解析前,
ldc对应的运行时常量池项类型是JVM_CONSTANT_UnresolvedString - 首次执行该
ldc时,JVM 才会触发解析:去StringTable查找已有字符串实例;若无,则在堆中新建,并将该实例引用存入StringTable - 解析完成后,该项被替换为
JVM_CONSTANT_String,后续再执行同一条ldc就直接返回已驻留的引用
为什么 new String("abc") 不走 ldc 解析流程
new String("abc") 的字节码通常包含 ldc #3(加载字面量)+ invokespecial(调用构造器)。其中 ldc 确实会触发字符串驻留,但 new 操作本身强制在堆上创建新对象,与 StringTable 中是否已有无关。
关键区别在于:是否“由 ldc 直接产生引用”。只有当字节码中某处 ldc 的结果被当作字符串值使用(如赋值给静态字段、作为方法参数传入、或直接参与字符串拼接的编译期优化),才可能让该字符串被驻留;而 new 构造出的对象永远是新堆对象,除非显式调用 .intern()。
-
ldc是“触发驻留”的常见入口,但不是唯一入口(String.intern()也能主动写入) -
ldc是否导致驻留,取决于它是否被用于生成字符串值——如果只是被丢弃或仅作类型检查,JVM 可能延迟甚至跳过解析(HotSpot 有惰性解析策略) - JDK 7+ 后,
StringTable在堆中,所以ldc解析出的引用指向的是堆内对象,不是方法区/元空间
ldc 和 intern() 在常量池寻址上的本质差异
ldc 是被动解析:按需、单向、不可控。而 intern() 是主动干预:无论来源,都强制查表并写入(若不存在)。
例如:String s = new String("abc").intern();,其行为等价于先执行 ldc #x(驻留 "abc"),再执行 new(建新对象),最后调用 intern() 返回已驻留引用。但若 "abc" 已由其他 ldc 驻留过,intern() 就直接返回那个引用。
-
ldc查的是运行时常量池 → 触发解析 → 查StringTable→ 写入(若未存在) -
intern()绕过常量池,直连StringTable:查表 + 条件插入,返回最终引用 - 两者都依赖同一个
StringTable结构,但入口路径和控制粒度完全不同 - 注意:
ldc解析失败不会抛异常,但intern()在极端内存压力下可能触发 GC 或 OOM(因要分配堆对象)
真正容易被忽略的是:JVM 对 ldc 的解析时机并不固定——它可能发生在类加载的解析阶段,也可能延迟到第一次执行该指令时(称为“懒解析”)。这意味着,即使两个类都声明了相同的字符串字面量,它们的驻留行为也可能因加载/执行顺序不同而出现时间差,进而影响 == 判断结果。这不是 bug,而是 JVM 为启动性能做的权衡。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











