jvm中的常量池分为三种
1.类文件常量池(Class Constant Pool) 也称静态常量池
2.运行时常量池(Runtime Constant Pool)
3.字符串常量池(String Constant Pool)
1.类文件常量池
我们写的每一个Java类被编译后,就会形成一份class文件(每个class文件都有一个class常量池)。 class文件中除了包含类的版本、字段、方法、接口等描述信息外,还有一项信息就是常量池(constant pool table),用于存放编译器生成的各种字面量(Literal)和符号引用(Symbolic References)。
字面量包括:1.文本字符串 2.八种基本类型的值 3.被声明为final的常量等;
符号引用包括:1.类和方法的全限定名 2.字段的名称和描述符 3.方法的名称和描述符。
2. 运行时常量池
运行时常量池(Runtime Constant Pool)是方法区的一部分。jdk1.8以前存在于永久代,jdk1.8之后存在于元空间。静态常量池中的内容,在类加载后会被存放到方法区的运行时常量池中。运行时常量池相对于Class文件常量池的另外一个重要特征是具备动态性,Java语言并不要求常量一定只有编译期才能产生,也就是说,并非预置入Class文件中静态常量池的内容才能进入方法区运行时常量池,运行期间也可以将新的常量放入池中。
3.字符串常量池
字符串常量池存在运行时常量池之中(在JDK7之前存在运行时常量池之中,在JDK7已经将其转移到堆中)。字符串常量池的存在使JVM提高了性能和减少了内存开销。
字符串常量池可以理解为是分担了部分运行时常量池的工作。和其他的对象分配一样,字符串耗费高昂的时间与空间代价,作为最基础的数据类型,大量频繁的创建字符串,极大程度地影响程序的性能,为了提高性能和减少内存开销,在实例化字符串常量的时候进行了一些优化,为字符串开辟一个字符串常量池,类似于缓存区。实现该优化的基础是因为字符串是不可变的,可以不用担心数据冲突进行共享。运行时实例创建的全局字符串常量池中有一个表,总是为池中每个唯一的字符串对象维护一个引用。
加载时,对于class文件的静态常量池,字符串字面量会进入到当前类的运行时常量池,但不会立即进入全局的字符串常量池(即在字符串常量池中并没有相应的引用,在堆中也没有对应的对象产生),字符串常量池是lazy resolve的,在第一次引用该项的ldc指令被第一次执行到的时候才会resolve,但这个过程我们可以不关注。可以认为当类加载完成后,运行代码String str1="abc"或String str2=new String(“abc”)时,字面量“abc”已经存在于字符串常量池。jdk7字符串常量池以后存在于堆中。
说明:
HotSpot虚拟机在jdk8以前是用永久代(Permanent Generation)实现方法区,永久代和方法区这两者不是等价的,对于其他虚拟机实现,譬如BEA JRockit、IBM J9等来说,是不存在永久代的概念的。因为仅仅是当时的HotSpot虚拟机设计团队选择把收集器的分代设计扩展至方法区,或者说使用永久代来实现方法区而已,这样使得HotSpot的垃圾收集器能够像管理Java堆一样管理这部分内存,省去专门为方法区编写内存管理代码的工作。
在JDK1.7之前 运行时常量池逻辑包含字符串常量池存放在方法区, 此时hotspot虚拟机对方法区的实现为永久代。静态常量池在Class
文件中。
在JDK1.7 字符串常量池被从方法区拿到了堆(方法区是堆的一个逻辑分区)中, 这里没有提到运行时常量池,也就是说字符串常量池被单独拿到堆,运行时常量池剩下的东西还在方法区, 也就是hotspot中的永久代。静态常量池在Class
文件中。
在JDK1.8及之后 hotspot移除了永久代用元空间(Metaspace)取而代之, 这时候字符串常量池还在堆, 运行时常量池还在方法区, 只不过方法区的实现从永久代变成了元空间(Metaspace)。静态常量池在Class
文件中。
如下图Java堆内存结构,注意,在Java虚拟机规范将永久代(方法区)中描述为Java堆的一个逻辑部分。