跳转至

字符串常量是什么时候进入到字符串常量池的?

🤔 字符串常量是什么时候进入字符串常量池的?

这个问题涉及 JVM 的底层机制,分两种情况:字面量常量和 intern() 动态添加,时机完全不同。


🧩 核心结论

查看内嵌表格


📋 展开说细节

双引号字面量:类加载的解析阶段

当你写 String s = "hello"; 时,"hello" 作为符号引用写在 .class 文件的常量池表(Constant Pool Table)里。JVM 在类加载的解析阶段(属于 Linking 过程)会去字符串常量池查是否有这个字符串,如果没有,就在池中创建它,然后把符号引用替换为直接引用。

所以,字面量不是等到运行到这一行代码时才进池,而是在类加载阶段就已经进去了。

2. new String("xxx"):字面量 + 堆对象,分离处理

new String("hello") 实际发生了:

  • 在类加载阶段,字面量 "hello" 已经进入常量池。

  • 在运行时,new 指令在 Java 堆上创建一个新的 String 对象,这个对象的 value 字符数组指向常量池中 "hello" 的字符数组。堆上的这个对象不在常量池里,它是一个普通堆对象。

所以 new String("hello") == "hello" 返回 false,因为一个是堆对象,一个是常量池对象。

3. intern():运行时动态入池的唯一主动手段

intern() 会去常量池找是否已经有这个字符串:

  • 如果有,直接返回常量池的引用。

  • 如果没有,JDK 7 及以后不会在常量池复制一份,而是把堆中当前对象的引用存入常量池(因为常量池从永久代移到了堆里,可以存引用了)。

这也就是为什么 new String("hello").intern() == "hello" 返回 true,因为 intern() 返回的就是常量池里那个字面量的引用。

常量表达式拼接:编译期直接搞定

String s = "a" + "b"; 这种纯字面量拼接,javac 在编译期就直接算出了 "ab",所以它等价于 String s = "ab";,同样在类加载阶段就进了常量池。这也是为什么 "a"+"b" == "ab" 返回 true

但如果拼接里有变量:String s = "a" + someVariable; 就不行了,那是运行时 StringBuilder 拼接,产生新的堆对象。


🧠 时序示意:类加载 + 运行时的交互

image.png