JVMS §5.1 说
Java 虚拟机为每个类和接口维护一个运行时常量池 (§2.5.5)。
术语“for each”表示不存在一个运行时常量池,但每个类或接口都有自己的专用池。后面的句子更清楚地表明,这个数据结构对应于一个类文件的常量池。
类或接口 (§4.4) 的二进制表示形式的 constant_pool 表用于在类或接口创建时构造运行时常量池 (§5.3)。
应该清楚的是,这种按类结构与用于在整个运行时规范化字符串实例的单一全局数据结构不同。
但是由于类文件中字符串常量的所有使用都表示为类文件常量池的索引,然后用于构造类的运行时常量池,这些使用与全局数据结构之间存在关系.如上所述within §5.1
运行时常量池中的静态常量也是根据每个条目的结构从constant_pool表中的条目导出的:
- 字符串常量是
reference 到类String 的实例,并且派生自CONSTANT_String_info 结构(§4.4.3)。为了派生一个字符串常量,Java 虚拟机检查CONSTANT_String_info 结构给出的代码点序列:
- 如果方法
String.intern 先前已在类String 的实例上调用,该实例包含与CONSTANT_String_info 结构所给出的相同的Unicode 代码点序列,则字符串常量是reference String 类的相同实例。
- 否则,将创建一个新的类
String 实例,其中包含CONSTANT_String_info 结构给出的Unicode 代码点序列。字符串常量是新实例的reference。最后,在新实例上调用方法 String.intern。
因此,正式地,与类中使用的字符串常量对应的 String 实例是该类的运行时常量池的一部分,但根据 String.intern 进行初始化,以确保每个类在其池中都有规范化的字符串实例。
但是这种关系只有一个方向。当应用程序代码显式调用String.intern() 时,它不会访问类的运行时常量池。甚至不清楚我们应该访问哪个运行时常量池。
所以intern() 与运行时常量池无关,至少与其他调用者无关。
混淆的来源是 JVM 用于实现intern() 的数据结构在 JVMS 或 JLS 中根本没有名称。所以,没有正式的名字,不同的名字就会出现在不同的媒体上。例如,API documentation of intern() 表示
一个字符串池,最初是空的,由 String 类私下维护。
它通常是某种哈希表,但术语“池”与其目的相符,并且由于它存在于运行时,因此人们提出容易与 JVMS §5.1 的运行时常量池混淆的术语也就不足为奇了.
因此,在与另一位开发人员展开激烈讨论之前,重要的是要澄清是否每个人都在谈论同一个池。
作为附录,我在上面说过String正式是运行时常量池的一部分,因为这涉及到实现的特定方面。
原则上,JVM 可以在创建池时使用 String 实例初始化类的运行时常量池的所有字符串条目。但正如 this answer 所展示的,广泛使用的 HotSpot JVM 并非如此,它在第一次使用而不是类初始化时查找或创建 String 实例。
这意味着运行时常量池包含某种形式的原始字符数据,而不是对String 实例的引用。一旦构造了String,它就会被代码引用和重用,但是运行时常量池是否被修改为现在引用String或字节码指令(ldc)保留它,无法观察到Java 应用程序。我们只能观察到,只要类存在,String 实例就不会被垃圾回收。