java常量池
概述
java 包括三种常量池,分别是 字符串常量池、Class 常量池(也叫常量池表)和运行时常量池。
字符串常量池(String Pool)
String Pool 是 JVM 实例全局共享的,而 Runtime Constant Pool 是每个类都有一个。
JVM 用一个哈希表记录对常量池的引用。
String Pool 在 JDK1.7 之前是存放在方法区中的,JDK1.7 移入到堆中。可以测试下往List中无限放入String,看 JDK各个版本的异常信息。JDK6 是PermGen Space内存溢出,JDK7 和 JDK8 都是Java heap space内存溢出。
常量池表(Constant Pool Table)
为了让 java 语言具有良好的跨平台性,java 团队提供了一种可以在所有平台上使用的中间代码——字节码(byte code),字节码需要在虚拟机上运行。像 Groovy、JRuby、Jython、Scala等,也会编译成字节码,也能够在 Java 虚拟机上运行。
java 文件会编译成 Class 文件,Class 文件包含了 Java 虚拟机指令集和符号表以及其他辅助信息。Class文件中除了包含类的版本、字段、方法、接口等描述信息外,还有一项信息(constant pool table),用于存放编译器生成的各种字面量(Literal) 和符号引用(Symbolic References)。也就是说常量池表示属于 Class 字节码文件中的一类结构化数据。
字面量(literal)
在计算机科学中,字面量(literal)是用于表达源代码中一个固定值的表示法(notation)。几乎所有计算机编程语言都具有对基本值的字面量表示,诸如:整数、浮点数以及字符串;而有很多也对布尔类型和字符类型的值也支持字面量表示;还有一些甚至对枚举类型的元素以及像数组、记录和对象等复合类型的值也支持字面量表示法。
上述是计算机科学对字面量的解释,在 Java 中,字面量包括:1.文本字符串 2.八种基本类型的值 3.被声明为 final 的常量等。
字面量只可以右值出现。如 String s = "helloword"
,s 为左值,helloword 为右值,helloword 为字面量。
符号引用(Symbolic References)
符号引用是编译原理的概念,是相对于直接引用来说的。在 Java中,符号引用包括:1.类和方法的全限定名 2.字段的名称和描述符 3.方法的名称和描述符。
常量池表的作用
常量池表(Class 常量池)是 Class 文件的资源仓库,保存了各种常量。在《深入理解Java虚拟机》中有这样的描述:
Java代码在进行Javac编译的时候,并不像C和C++那样有“连接”这一步骤,而是在虚拟机加载Class文件的时候进行动态连接。也就是说,在Class文件中不会保存各个方法、字段的最终内存布局信息,因此这些字段、方法的符号引用不经过运行期转换的话无法得到真正的内存入口地址,也就无法直接被虚拟机使用。当虚拟机运行时,需要从常量池获得对应的符号引用,再在类创建时或运行时解析、翻译到具体的内存地址之中。
运行时常量池(Runtime Constant Pool)
JVM 运行时内存中方法区的一部分,所以也是全局共享的,是运行时的内容
运行时常量池相对于 Class 常量池一大特征就是其具有动态性,Java 规范并不要求常量只能在运行时才产生,也就是说运行时常量池中的内容并不全部来自 Class 常量池,Class 常量池并非运行时常量池的唯一数据输入口;在运行时可以通过代码生成常量并将其放入运行时常量池中
同方法区一样,当运行时常量池无法申请到新的内存时,将抛出 OutOfMemoryError 异常。
这部分数据绝大部分是随着 JVM 运行,从常量池表转化而来,每个 Class(不是 Java 对象) 都对应一个运行时常量池。(上面说绝大部分是因为:除了Class 中常量池内容,还可能包括动态生成并加入这里的内容)