字符串常量池核心在于复用与存放位置:jdk1.7+起位于堆中,字面量直接入池并复用;new创建先查池再堆分配新对象;intern()在池中无时存入堆对象引用;jdk1.6则在永久代,易oom。

字符串在 JVM 常量池中的存放方式,核心在于“是否复用”和“存放在哪”,不是简单地“一存了之”。关键要看创建方式、JDK 版本,以及字符串内容是否已存在。
字符串字面量直接存入字符串常量池(堆中)
像 String s = "hello"; 这样的写法,编译期就确定的字符串字面量,JVM 在类加载阶段会把 "hello" 加入字符串常量池。从 JDK 1.7 开始,这个池已不在方法区/永久代,而是明确位于堆内存中。
- 首次出现时:检查池中无相同内容 → 在堆中创建字符串对象,并将引用放入字符串常量池
- 再次出现时:池中已存在 → 直接复用已有引用,不新建对象
- 所以 "abc" == "abc" 返回 true,因为指向堆中同一个池内对象
new String("xxx") 会额外在堆中创建对象
使用 new 创建字符串时,行为分两步:
- 先确保字面量 "xxx" 已在字符串常量池中(若无则加入)
- 再在堆中新分配一个 String 对象,其 value 字段指向常量池中已有的 char[](JDK 9+ 是 byte[])
也就是说,new String("abc") 至少涉及一次常量池查找 + 一次堆内存分配。它生成的对象与常量池中那个不是同一个实例,所以 == 比较结果为 false。
intern() 方法决定字符串是否进入或关联常量池
String.intern() 是显式干预字符串常量池的关键方法:
- 如果池中已有相同内容的字符串 → 返回池中那个字符串的引用
- 如果池中没有 → 将当前字符串对象引用加入池中(JDK 7+),并返回该引用
注意:JDK 7+ 的 intern 不再复制字符串,而是直接把堆中已存在的字符串对象引用存入池;而 JDK 6 会把字符串拷贝一份到永久代的池里。这也是为什么 new String("java").intern() == "java" 在 JDK 7+ 是 true,但在 JDK 6 是 false(因为 "java" 本身已在池中,而 new 出的对象是另一份)。
字符串常量池位置随 JDK 版本变化
存放位置直接影响 GC 行为和调优思路:
- JDK 1.6 及以前:字符串常量池在方法区(永久代),受 PermSize 限制,容易 OOM
- JDK 1.7:字符串常量池被移到堆内存,和普通对象一样可被 GC 回收
- JDK 1.8+:永久代移除,方法区由元空间(Metaspace)替代;字符串常量池仍在堆中,运行时常量池(含类/方法等符号信息)才在元空间










