java字符串常量池通过编译期预存、运行期查表和引用共享实现隐式复用,字面量自动入池,new创建对象不共享,intern()可手动将堆中字符串引用加入池中。

Java 通过常量池对部分引用类型做隐式复用,核心是避免重复创建、减少堆内存占用和 GC 压力。这种优化不靠“自动回收”,而是靠编译期预存 + 运行期查表 + 引用共享来实现。
字符串字面量自动进池
用双引号声明的字符串(如 "abc")在编译时就进入 class 文件常量池,类加载时被提取到 JVM 全局的字符串常量池(即 StringTable)。JVM 每次遇到相同字面量,都会复用已有引用:
- String s1 = "hello"; String s2 = "hello"; → s1 == s2 为 true
- 但 new String("hello") 总是在堆中新建对象,即使内容相同,== 比较也为 false
- 拼接含变量的表达式(如 s1 + s2)不会进池,除非编译期可确定(如 "a" + "b" 会被优化为 "ab")
包装类缓存有范围限制
Integer、Byte、Short、Long、Character 等包装类也采用类似思路,但不是通过字符串常量池,而是各自维护静态缓存数组(如 IntegerCache):
- Integer i1 = 100, i2 = 100; → i1 == i2 为 true(默认缓存 [-128, 127])
- Integer i3 = 200, i4 = 200; → i3 == i4 为 false(超出缓存范围,各自 new)
- 可通过 JVM 参数 -XX:AutoBoxCacheMax=2000 扩大 Integer 缓存上限(仅对 Integer 有效)
intern() 是手动入池的桥梁
String.intern() 是唯一能将堆中字符串“拉进”常量池的公开方法:
- 调用时,JVM 查 StringTable:若已存在相同内容的字符串引用,直接返回该引用
- 若不存在,则将当前字符串对象的引用加入池中,并返回该引用
- 注意:JDK 7+ 后,intern() 不再复制对象,只是存堆中对象的引用,效率更高
- 示例:String s = new String("xyz").intern(); 之后再写 String t = "xyz";,则 s == t 为 true
常量池不是万能的
理解哪些情况不触发复用,才能避免误判行为:
- 运行期拼接含变量的字符串(如 "a" + s),结果是新 StringBuilder 构造的对象,不在池中
- 反射、动态代理、Class.forName() 加载的类,其符号引用不参与当前类常量池的复用逻辑
- final static 字段赋值为字面量(如 final static String S = "x";),该字面量仍进池,但字段本身是编译期常量,可能被内联
- 字符串常量池只存引用(JDK 7+),不存对象本体;对象始终在堆中,池只是“登记簿”
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











