字符串常量是什么时候进入到字符串常量池的?
🤔 字符串常量是什么时候进入字符串常量池的?
这个问题涉及 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 拼接,产生新的堆对象。
🧠 时序示意:类加载 + 运行时的交互¶
