字符串字面量存于字符串池,是运行时常量池的一部分,jdk 7+起逻辑归属堆;仅双引号字面量和intern()入池;new string("abc")创建堆对象和池中字面量,==比较结果取决于引用位置。

字符串字面量到底存在哪?String s = "abc" 的内存位置
它进的是**字符串池(String Pool)**,而字符串池是**运行时常量池(Runtime Constant Pool)** 的一部分,后者又属于方法区(JDK 7+ 后逻辑上归入堆)。不是所有常量都进字符串池——只有双引号直接写的字面量、以及显式调用 String.intern() 的才会进去。
常见错误现象:new String("abc") 创建了两个对象:一个在堆(new 出的),一个在字符串池(字面量 "abc")。但 == 比较时,前者指向堆,后者指向池,结果为 false。
-
"abc" == "abc"→true(同池内引用) -
new String("abc") == "abc"→false(堆 vs 池) -
new String("abc").intern() == "abc"→true(强制入池后引用一致)
Class.forName 和 ClassLoader.loadClass 对常量池加载时机的影响
类加载过程中,常量池内容(包括类名、字段名、方法签名、字符串字面量等)是在**解析阶段**被加载进运行时常量池的。但触发时机不同:
-
Class.forName("X")默认会初始化类(执行static块),也就意味着常量池已完全解析并可用 -
ClassLoader.loadClass("X")只加载、不初始化,此时常量池虽已载入,但部分符号引用可能尚未解析(比如依赖未加载类的字段引用)
性能影响:过早触发初始化可能引发不必要的静态资源加载或异常;若仅需反射获取类结构(如注解扫描),用 loadClass 更轻量。
Java项目代码review工具。分析Git变更+完整调用链路上下文,推断业务需求,进行多维度评分和分类汇总,生成完整PRD文档。包含细粒度Java代码审查清单(Null安全、异常处理、Streams、并发、equals/hashCode、资源管理、API设计、性能、MyBatis/ORM、事务边界、SQL/DD...
JDK 6/7/8 字符串池位置变化带来的实际后果
字符串池从永久代(PermGen)移到堆(Heap),不是纯“搬家”,它改变了 GC 行为和 OOM 类型:
- JDK 6:字符串池在永久代,
OutOfMemoryError: PermGen space常见于大量intern() - JDK 7+:字符串池在堆,受堆大小限制,
OutOfMemoryError: Java heap space更常见;但能被常规 GC 回收(只要没强引用) - 注意:
intern()在 JDK 7+ 后不再复制字符串对象,而是直接把首次出现的实例引用存入池——所以new String("a").intern() == "a"在 JDK 7+ 是true,JDK 6 是false
编译期常量池 vs 运行时常量池:为什么 final String s = "a" + "b" 能优化,而 final String s = getA() + getB() 不行
编译期常量池(.class 文件里的 ConstantPool)只存编译时确定的字面量和符号引用;运行时常量池是 JVM 加载类时对它的映射和扩展,支持运行时动态加入(如 intern、动态生成的类名)。
所以:
-
final String x = "a"; final String y = "b"; String z = x + y;→ 编译器识别为常量表达式,直接优化成"ab",进字符串池 -
final String x = getA(); final String y = getB(); String z = x + y;→ 方法调用无法在编译期求值,拼接结果在堆上生成新对象,不会自动进池
容易被忽略的一点:哪怕变量声明为 final,只要其值来自运行时计算(哪怕逻辑上“不变”),就进不了编译期常量池,也不会触发字符串池优化。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










