常量池是编译期生成的符号与字面量索引表,存在于.class文件中,加载后映射为运行时常量池;字符串常量池则是运行时全局缓存,二者位置、作用均不同。

常量池不是一块内存区域,而是一张编译期生成的“符号与字面量索引表”——它存在于 .class 文件里,加载后映射为运行时常量池;字符串缓存(字符串常量池)是另一回事,专用于 String 对象重用,位置和行为都和它不同。
常量池在哪儿?怎么看到它的真实结构
它就藏在你编译出的 .class 文件开头部分,是 JVM 规范定义的二进制表格结构,不是 Java 代码能直接 new 出来的对象。想看清它,得用工具反编译:
- 用
javap -v MyClass查看详细字节码,输出里Constant pool:下面那一长串就是——每行带 #编号,比如#1 = Methodref #2.#3表示这是一个方法引用,指向 #2 类、#3 方法名+描述符 - 注意:
CONSTANT_Utf8_info存的是 UTF-8 编码的原始字节,不是String实例;CONSTANT_String_info存的只是对CONSTANT_Utf8_info的索引,还不是字符串对象本身 - 别误以为
javap输出里的String #5就代表堆里已存在该字符串——它只说明“编译时写了这个字面量”,真正创建对象发生在类加载或首次触发ldc指令时
字符串常量池 vs Class 文件常量池:为什么 String.intern() 有时没效果
这是最常混淆的点:两者根本不在一个层级上。Class 常量池是静态的、每个类一份的元数据;字符串常量池是全局的、运行时维护的引用缓存池(JDK7+ 在堆中)。
-
"abc"字面量在编译时写入 Class 常量池,类加载时若该字符串尚未在字符串池中,则把新创建的String对象引用放入池中 -
new String("abc")一定在堆上新建对象,哪怕字符串池里已有"abc";调用.intern()才会检查池中是否存在,有则返回池中引用,否则将当前对象引用加入池并返回 - 陷阱:JDK6 中字符串池在永久代,
intern()可能触发 Full GC;JDK7+ 移到堆后更安全,但大量调用仍可能引发 Minor GC 频率上升 - 常见错误现象:
new String("test").intern() == "test"在 JDK7+ 为true,但在 JDK6 是false(因为前者返回池中引用,后者返回堆中新对象)
哪些东西会被放进 Class 常量池?哪些不会
一句话:所有编译期能确定的字面量和符号引用都会进,但运行期计算值不会。
立即学习“Java免费学习笔记(深入)”;
- 会进的:
final int x = 100;、static final String s = "hello";、方法签名中的类名/字段名/描述符(如java/lang/String.toString:()Ljava/lang/String;) - 不会进的:
final int y = System.currentTimeMillis();(运行期才知值)、String s2 = "hel" + getRuntimeStr();(拼接含变量,无法在编译期折叠) - 注意:
String s3 = "hel" + "lo";会被编译器优化成"hello",所以会进常量池;但"hel" + new StringBuilder().append("lo")不会 - 性能影响:常量池过大(比如生成大量动态类或反射频繁注册)会拖慢类加载速度,因为 JVM 要逐条解析并解析符号引用(尤其是类名需触发类加载)
真正容易被忽略的是:常量池本身不占堆内存,但它驱动的符号解析过程可能触发类加载、链接甚至初始化——这意味着你以为只是读个字面量,背后可能已执行了 static {} 块。查问题时,别只盯着堆和栈,得顺藤摸到 .class 文件和类加载阶段。









