详解JVM之运行时常量池
class文件中的常量池
之前我们在讲class文件的结构时,提到了每个class文件都有一个常量池,常量池中存了些什么东西呢?
字符串常量,类和接口名字,字段名,和其他一些在class中引用的常量。
运行时常量池
但是只有class文件中的常量池肯定是不够的,因为我们需要在jvm中运行起来。
这时候就需要一个运行时常量池,为jvm的运行服务。
运行时常量池和class文件的常量池是一一对应的,它就是class文件的常量池来构建的。
运行时常量池中有两种类型,分别是symbolic references符号引用和static constants静态常量。
其中静态常量不需要后续解析,而符号引用需要进一步进行解析处理。
什么是静态常量,什么是符号引用呢? 我们举个直观的例子。
string site="www"
上面的字符串"www.com"可以看做是一个静态常量,因为它是不会变化的,是什么样的就展示什么样的。
而上面的字符串的名字“site”就是符号引用,需要在运行期间进行解析,为什么呢?
因为site的值是可以变化的,我们不能在第一时间确定其真正的值,需要在动态运行中进行解析。
静态常量详解
运行时常量池中的静态常量是从class文件中的constant_pool构建的。可以分为两部分:string常量和数字常量。
string常量
string常量是对string对象的引用,是从class中的constant_string_info结构体构建的:
constant_string_info { u1 tag; u2 string_index; }
tag就是结构体的标记,string_index是string在class常量池的index。
string_index对应的class常量池的内容是一个constant_utf8_info结构体。
constant_utf8_info { u1 tag; u2 length; u1 bytes[length]; }
constant_utf8_info是啥呢?它就是要创建的string对象的变种utf-8编码。
我们知道unicode的范围是从0x0000 至 0x10ffff。
变种utf-8就是将unicode进行编码的方式。那是怎么编码呢?
从上图可以看到,不同的unicode范围使用的是不同的编码方式。
注意,如果一个字符占用多个字节,那么在class文件中使用的是 big-endian 大端优先的排列方式。
如果字符范围在ffff之后,那么使用的是2个3字节的格式的组合。
讲完class文件中constant_string_info的结构之后,我们再来看看从constant_string_info创建运行时string常量的规则:
规则一:如果string.intern之前被调用过,并且返回的结果和constant_string_info中保存的编码是一致的话,表示他们指向的是同一个string的实例。
规则二:如果不同的话,那么会创建一个新的string实例,并将运行时string常量指向该string的实例。最后会在这个string实例上调用string的intern方法。调用intern方法主要是将这个string实例加入字符串常量池。
数字常量
数字常量是从class文件中的constant_integer_info, constant_float_info, constant_long_info和 constant_double_info 构建的。
符号引用详解
符号引用也是从class中的constant_pool中构建的。
对class和interface的符号引用来自于constant_class_info。
对class和interface中字段的引用来自于constant_fieldref_info。
class中方法的引用来自于constant_methodref_info。
interface中方法的引用来自于constant_interfacemethodref_info。
对方法句柄的引用来自于constant_methodhandle_info。
对方法类型的引用来自于constant_methodtype_info。
对动态计算常量的符号引用来自于constant_methodtype_info。
对动态计算的call site的引用来自于constant_invokedynamic_info。
string pool字符串常量池
我们在讲到运行时常量池的时候,有提到string常量是对string对象的引用。那么这些创建的string对象是放在什么地方呢?
没错,就是string pool字符串常量池。
这个string pool在每个jvm中都只会维护一份。是所有的类共享的。
string pool是在1.6之前是存放在方法区的。在1.8之后被放到了java heap中。
注意,string pool中存放的是字符串的实例,也就是用双引号引起来的字符串。
那么问题来了?
string name = new string("www");
到底创建了多少个对象呢?
总结
class文件中常量池保存的是字符串常量,类和接口名字,字段名,和其他一些在class中引用的常量。每个class都有一份。
运行时常量池保存的是从class文件常量池构建的静态常量引用和符号引用。每个class都有一份。
字符串常量池保存的是“字符”的实例,供运行时常量池引用。
以上就是详解jvm之运行时常量池的详细内容,更多关于jvm之运行时常量池的资料请关注其它相关文章!