一、有以下三大常量池:
Class文件中的常量池
每个.java文件编译成.class文件后,都会产生当前类独有的常量池,即我们介绍的第一大常量池。这个常量池主要存放两类常量
- 字面量(Literal)
包括文本字符串、final类型的常量 - 符号引用(Symbolic References)
包括三类
- 类和接口的全限定名
- 字段的名称和描述符
- 方法的名称和描述符
- 字面量(Literal)
运行时常量池(Runtime Constant Pool)
是方法区的一部分。
Class文件中有类的版本、字段、方法、接口等描述信息,还有常量池(Constant Pool Table),用于存放编译器生成的字面量和符号引用,而这部分内容,在类加载后进入方法区的运行时常量池中存放。不过不是只有在Class文件的常量池中的内容才能放入运行时常量池,在运行期间也可以将新常量放进去(比如String.intern()方法),这也是运行时常量池和Class文件常量池的一个重要区别,即运行时常量池具有动态性。全局字符串常量池
HotSpot VM里,有一个记录interned string的全局表StringTable,它本质上就是个HashSet。这是个纯运行时的结构,而且是惰性(lazy)维护的。它只存储对java.lang.String实例的引用,而不存储String对象的内容。一般我们说一个字符串进入了全局的字符串常量池其实是说在这个StringTable中保存了对它的引用,反之,如果说没有在其中就是说StringTable中没有对它的引用。
总结一下就是:
- 每个生成的Class文件中都有一个常量池
- 每个VM中有且仅有一个全局字符串常量池,存的是字符串的引用
- 每个Class文件中也都有一个运行时常量池
二、为何要有常量池?
我们知道,在JVM运行时数据区中有一块地方是Java虚拟机栈,虚拟机栈中,每个Java方法执行的同时会创建一个栈帧(Stack Frame),这个栈帧用来存局部变量表、操作数栈、动态链接、方法出口等信息
而JVM为了方便链接,便使用常量池来保存跟踪当前类中引用的类和成员变量、成员方法。
每个栈帧都包含一个运行时常量池的引用,这个引用指向当前栈帧所要执行的方法,JVM用这个引用来进行动态链接。
在这里就可以看出Java与C/C++ 的一个重要区别:
Java中的链接是在程序运行时动态进行的,而C/C++中,编译器将多个编译期编译的文件链接成一个可执行文件或dll文件,在链接阶段,符号引用被解析为实际地址。
三、为何要有字符串常量池?
- 字符串和其他对象的分配一样,需要高昂的时间和空间成本,而字符串作为非常频繁使用的一种类型,如果不想办法优化,将会极大影响程序的性能。
- JVM为了提高性能和减小开销,在实例化字符串常量时做了优化:
- 为字符串分配一个字符串常量池,类似于缓存
- 创建字符串常量时首先检查池中有无该字符串
- 如果该字符串已存在于常量池中,则返回引用实例;否则,再实例化该字符串并放入池中
- 实现的基础:
- 字符串不可变,所以共享时不会有数据冲突
- 运行时有一个全局字符串常量池,为池中的每个字符串维护一个引用,根据这个引用可以找到具体的String对象