JVM的常量池:什么是字符串常量池、运行时常量池、Class常量池

在 JAVA 语言中有8中基本类型和一种比较特殊的类型String。这些类型为了使他们在运行过程中速度更快,更节省内存,都提供了一种常量池的概念。常量池就类似一个JAVA系统级别提供的缓存。首先,比较有名的常量池有两个,分别是运行时常量池和静态常量池,但是目前可以被称为常量池的有三个:字符串常量池、运行时常量池、静态常量池。

这三个常量池的变动的关键时刻是在JDK1.7的时候:

 

(1) 在JDK1.7之前:运行时常量池逻辑包含字符串常量池存放在方法区,所以此时常量池只分为运行时常量池和静态常量池。

 

(2)在JDK1.7时: 字符串常量池被从方法区拿到了堆中,只是字符串常量池被单独拿到堆, 而运行时常量池剩下的东西还在方法区。至于为什么移到堆内,可能是由于方法区的内存空间太小。

 

(3)在JDK1.8时: hotspot移除了永久代改用元空间 这时字符串常量池依然在堆中,运行时常量池依然在方法区, 只不过方法区的实现从永久代变成了元空间

 

也就是说从JDK1.7开始运行时常量池和静态常量池之外又多了一个字符串常量池,它被放在堆中。

 

一、字符串常量池(String Constant Pool)

 

在HotSpot VM里实现的string pool功能的是一个StringTable类,它是一个Hash表,里面存的是驻留字符串(也就是用双引号括起来的)的引用(而不是驻留字符串实例本身),也就是说在堆中的某些字符串实例被这个StringTable引用之后就等同被赋予了”驻留字符串”的身份。这个StringTable在每个HotSpot VM的实例只有一份,被所有的类共享。

在JDK1.6中,StringTable的长度是固定的,长度就是1009,字符串常量由一个一个字符组成,放在了StringTable上。因此如果放入String Pool中的String非常多,就会造成hash冲突,导致链表过长,当调用String#intern()时会需要到链表上一个一个找,会导致性能大幅度下降。

所以在JDK1.7中,对String#intern()进行了改变,因此String Pool中也可以存放放于堆内的字符串对象的引用。同时StringTable的长度可以通过参数指定:-XX:StringTableSize。

 

二、class常量池(Class Constant Pool)

 

当一个Java类被编译后,就会形成一份class文件,即类被编译后的数据,该文件中除了包含类的版本、字段、方法、接口等类的描述信息外,还有一项信息就是常量池。

在Class文件结构中,最头的4个字节用于存储魔数Magic Number(用于确定一个文件是否能被JVM接受),再接着4个字节用于存储版本号(前2个字节存储次版本号,后2个存储主版本号),再接着是用于存放常量的常量池,由于常量的数量是不固定的,所以常量池的入口放置一个U2类型的数据(constant_pool_count)来存储常量池容量计数值。每个class文件都有一个class常量池用于存放编译器生成的各种字面量和符号引用:

字面量包括:字面量相当于Java语言层面常量的概念,1.文本字符串 2.八种基本类型的值 3.被声明为final的常量

符号引用包括:符号引用则属于编译方面的概念,包括了如下三种类型的常量,1.类和方法的全限定名 2.字段的名称和描述符 3.方法的名称和描述符。

常量池的每一项常量都是一个表,一共有如下表所示的11种各不相同的表结构数据,这每个表开始的第一位都是一个字节的标志位(取值1-12),代表当前这个常量属于哪种常量类型。 

 

三、运行时常量池(Runtime Constant Pool)

 

当类加载到内存中后,jvm就会将class常量池中的内容存放到运行时常量池中,这时运行时常量池存在于内存中作为class常量池被加载到内存之后的版本,也就是说运行时常量池也是每个类都有一个,class常量池中存的并不是对象的实例,而是对象的符号引用值,在类的加载过程中会有解析过程,在解析阶段,会把符号引用替换为直接引用,解析的过程会去查询字符串常量池,以保证运行时常量池所引用的字符串与字符串常量池中是一致的。

在class文件常量池的符号引用会在不同的时候被转变为直接引用,比如说类的静态方法或私有方法,实例构造方法,父类方法,这是因为这些方法不能被重写其他版本,所以能在加载的时候就可以将符号引用转变为直接引用,而其他的一些方法会在这个方法被第一次调用的时候才将符号引用转变为直接引用。

与class常量池的不同之处除了符号引用会被解析为直接引用,还有关键一点在于运行时常量池相对于CLass文件常量池是具备动态性,并不是常量一定只有编译期才能产生,并非只有预置在Class文件常量池的内容才能进入运行时常量池,运行期间也可能将新的常量放入池中,可以通过String#intern()动态的添加字面量。

 

 

1.全局常量池在每个VM中只有一份,存放的是字符串常量的引用值。

2.class常量池是在编译的时候每个class都有的,在编译阶段,存放的是常量的符号引用。

3.运行时常量池是在类加载完成之后,将每个class常量池中的符号引用值转存到运行时常量池中,也就是说,每个class都有一个运行时常量池,类在解析之后,将符号引用替换成直接引用,与全局常量池中的引用值保持一致。

 

关于对字符串常量的使用和使用字符串常量时和常量池之间的关系,请看JVM的常量池:String.intern()的理解以及字符串常量池解析

 

猜你喜欢

转载自blog.csdn.net/ZytheMoon/article/details/105861682