常量池是JVM堆中真实存在的内存区域,用于复用相同字面量字符串,其安全运行依赖String不可变性:内容锁死、无修改方法、所有“修改”均返回新实例,确保共享引用不被篡改。

常量池是理解字符串唯一性和不可变性的核心枢纽。它不是抽象概念,而是 JVM 中真实存在的内存区域,直接决定字符串对象是否复用、何时复用、为何必须不可变。
常量池让相同字面量只存一份
当你写 String a = "hello"; String b = "hello";,JVM 不会创建两个对象。它先查常量池:发现 "hello" 已存在,就让 a 和 b 都指向池中同一个实例。所以 a == b 为 true —— 它们地址相同,内容共享。
这种复用能大幅减少堆内存压力。如果每次写 "user_id" 都新建对象,成千上万次调用后,内存里会堆满重复字符串。
关键前提是:这个 "hello" 必须永远不变。否则,a 修改了它,b 就会读到脏数据。
不可变性是常量池安全运行的契约
字符串不可变,不是“变量不能重新赋值”,而是对象实例的内容锁死。源码中 private final char[] value 只是基础保障;真正起作用的是整个类设计:
- String 类被 final 修饰,无法被继承篡改
- 没有提供任何 public 方法修改 value 数组内容
- 所有看似修改的操作(如 replace、substring、+ 拼接)都返回新 String 实例,原对象毫发无伤
正因如此,JVM 才敢放心把 "https://api.example.com" 放进常量池,让网络层、日志模块、权限校验同时引用它——谁也改不了它。
new String("xxx") 绕过了常量池,但 intern() 可以补救
String c = new String("hello"); 这行代码做了两件事:
- 先确保字面量 "hello" 在常量池中(若没有则放入)
- 再在堆上全新构造一个 String 对象,其 value 引用池中那个 "hello" 的数组
所以 c == a 是 false(堆 vs 常量池),但 c.equals(a) 是 true(内容相同)。
想让它也指向常量池?调用 c.intern():
- JDK7+ 后,intern() 把堆中字符串的引用登记进常量池(池中无则登记,有则返回已有引用)
- 注意:这不是复制内容,而是建立共享引用关系
编译期拼接进池,运行期拼接不进池
常量池只收纳编译期确定的字符串字面量组合:
- String s1 = "ab" + "cd"; → 编译器直接优化为 "abcd",进常量池
- String s2 = "ab"; String s3 = s2 + "cd"; → s2 是变量,拼接发生在运行期,结果在堆上,不进池
这意味着:
- 字符串拼接是否触发对象复用,取决于是否含变量
- 循环中用 + 拼接大量字符串,会持续在堆上创建新对象,应改用 StringBuilder

















