Java中只有满足编译期常量表达式的字符串字面量、基本类型静态final常量、类/方法/字段符号信息等才进入运行时常量池,用于内存复用、高效解析和动态链接。

Java中类的常量(如字符串字面量、基本类型编译期常量)被放入运行时常量池(Runtime Constant Pool),本质是为了实现内存复用、提升性能,并支持JVM对符号引用的解析和动态链接。这不是“所有常量都进常量池”,而是有明确规则的——只有满足特定条件的常量才会在类加载阶段被存入常量池。
哪些常量会进入常量池?
不是所有带final的变量都进常量池,关键看是否满足编译期可确定的常量表达式:
- 字符串字面量(如
"hello")一定进常量池;用new String("hello")创建的则不会(对象在堆中) -
public static final int MAX = 100;这类基本类型+编译期常量值的静态字段,其值会被直接“内联”进调用处,同时该字面量100也会存入常量池 -
public static final String NAME = "Tom";同样进常量池;但public static final String NAME = new String("Tom");不会(因为运行期才创建) - 类名、方法名、字段名、描述符等符号信息,也作为CONSTANT_Class_info、CONSTANT_NameAndType_info等结构存入常量池,供JVM链接时使用
常量池是“池”,但不是所有常量都共享
常量池是每个类或接口独立拥有的,存储在方法区(JDK 7+ 逻辑上在堆中,JDK 8+ 元空间)。不同类里相同的字符串字面量,在各自常量池中是独立条目;但通过String.intern()可手动将其映射到全局字符串表(String Table),实现跨类共享。
例如:
class A { static final String s = "abc"; }
class B { static final String t = "abc"; }A和B各自的常量池里都有一个"abc"条目,但它们默认不指向同一个
String实例(除非显式调用intern())。
常量池内容在类加载哪个阶段写入?
常量池数据在类加载的“准备”阶段之后、“解析”阶段之中完成初始化:
立即学习“Java免费学习笔记(深入)”;
- 准备阶段:为静态变量分配内存并设默认值(如
0、null) - 解析阶段:把常量池内的符号引用(如类名、方法名)替换为直接引用;此时字符串字面量等实际值也被加载进运行时常量池
- 注意:静态变量若引用了常量池中的字符串,其赋值动作(如
s = "abc")发生在“初始化”阶段,但"abc"本身早已在解析阶段进了常量池
为什么设计常量池?核心作用是什么?
常量池是JVM实现高效链接与运行时优化的基础设施:
- 节省内存:相同字面量只存一份(尤其字符串),避免重复对象
- 加速解析:方法调用、字段访问等指令只需持有一个常量池索引,比存储完整名称更紧凑
- 支持动态性:反射、Lambda、invokedynamic等机制依赖常量池中动态生成的引导方法和调用点信息
- 验证与安全:类文件校验时检查常量池引用是否合法,防止非法跳转或类型混淆
基本上就这些。常量池不是黑盒缓存,而是JVM链接模型的核心枢纽——它让编译后的字节码保持紧凑,又为运行时的灵活解析留出空间。理解它,才能真正看懂类加载、字符串比较、反射调用背后的逻辑。










