字符串常量、字符串常量池详解
- ✔️字符串常量池是如何实现的?
- ✔️字符串常量从哪来的?
- ✔️字符串常量是什么时候进入到字符串常量池的?
✔️字符串常量池是如何实现的?
字符串常量池 (String Constant Pool) 是Java中一块特殊的内存区域,用于存储字符串常量。
当程序中出现字符串常量时,Java编译器会将其放入字符串常量池中。字符串常量是不可变的,因此可以共享。如果字符串常量池中已存在相同内容的字符串,编译器会直接引用已存在的字符串常量而不会创建新的对象。
在HotSpot虚拟机中:
在JDK 1.6及之前的版本,字符串常量池通常被实现为方法区的一部分,即永久代(PermanentGeneration)
,用于存储类信息、常量池、静态变量、即时编译器编译后的代码等数据。
从JDK 1.7开始,字符串常量池的实现方式发生了重大改变。字符串常量池不再位于永久代,而是直接存放在堆 (Heap) 中,与其他对象共享堆内存。
之所以要挪到堆内存中,主要原因是因为永久代的 GC
回收效率太低,只有在FullGC
的时候才会被执行回收。但是Java中往往会有很多字符串也是朝生夕死的,将字符串常量池放到堆中,能够更高效及时地回收字符串内存。
✔️字符串常量从哪来的?
字符串常量池中的常量有以下几个来源:
1、字面量常量
在代码中直接使用双引号括起来的字符串字面值(如 String s =“Java”)会被认为是常量并且会在编译后进入
class
文件的常量池,并且在运行阶段,进入字符串常量池。这是最常见的字符串常量来源。
2、intern() 方法
String类提供了一个
intern()
方法,用于将字符串对象手动添加到字符串常量池中。调用intern()
方法时,如果字符串常量池中已经存在相同内容的字符串,将会返回常量池中的引用;如果不存在,则会在常量池中创建新的字符串。
✔️字符串常量是什么时候进入到字符串常量池的?
字符串常量池中的常量有两种来源,一种是字面量会在编译期先进入到Class常量池,然后再在运行期进去到字符串池,还有一种就是在运行期通过
intern
将字符串对象手动添加到字符串常量池中。
💡那么,Class常量池中的常量,是在什么时候被放进到字符串池的呢?
Java 的类加载过程要经历加载 (Loading)
、链接(Linking)
、初始化(Initializing)
等几个步骤,在链接这个步骤,又分为验证(Verification)
、准备(Preparation)
以及解析(Resolution)
等几个步骤。
在Java 虚拟机规范及 Java语言规范中都提到过:
《The Java Virtual Machine Specification》 5.4 Linking:
For example, a Java Virtual Machine implementation may choose to resolve each symbolic reference ina class or interface individually when it is used (“lazy” or “late” resolution), or to resolve them all atonce when the class is being verified (“eager” or “static” resolution)
《The Java Language Specification》 12.3 Linking of Classes and Interfaces
For example, an implementation may choose to resolve each symbolic reference in a class or interfaceindividually, only when it is used (lazy or late resolution), or to resolve them all at once while the classis being verified (static resolution). This means that the resolution process may continue. in someimplementations, after a class or interface has been initialized.
大致意思差不多,就是说,Java 虚拟机的实现可以选择只有在用到类或者接口中的符号引用时才去逐一解析他(延迟解析),或者在验证类的时候就解析每个引用(预先解析)。这意味着在一些虚拟机实现中,把常量放到常量池的步骤可能是延迟处理的。
对于 HotSpot 虚拟机来说,字符串字面量,和其他基本类型的常量不同,并不会在类加载中的解析阶段填充并驻留在字符串常量池中,而是以特殊的形式存储在运行时常量池中。只有当这个字符串字面量被调用时,才会对其进行解析,开始为他在字符串常量池中创建对应的 String 实例。
通过查看 HotSpotJDK 1.8 的ldc 指的源代码,也可以验证上面的说法。
ldc 指令表示
int
、float
或String
型常量从常量池推送至栈顶
所以,字符串常量,是在第一次被调用(准确的说是ldc
指令)的时候,进行解析并在字符串池中创建对应的String实
例的。