目录
简介
class 文件中的常量池
运行时常量池
静态常量详解
String 常量
数字常量
符号引用详解
String Pool 字符串常量池
总结
简介
JVM 在运行的时候会对 class 文件进行加载, 链接和初始化的过程. class 文件中定义的常量池在 JVM 加载之后会发生什么神奇的变化呢? 快来看一看吧.
class 文件中的常量池
之前我们在讲 class 文件的结构时, 提到了每个 class 文件都有一个常量池, 常量池中存了些什么东西呢?
字符串常量, 类和接口名字, 字段名, 和其他一些在 class 中引用的常量.
运行时常量池
但是只有 class 文件中的常量池肯定是不够的, 因为我们需要在 JVM 中运行起来.
这时候就需要一个运行时常量池, 为 JVM 的运行服务.
运行时常量池和 class 文件的常量池是一一对应的, 它就是 class 文件的常量池来构建的.
运行时常量池中有两种类型, 分别是 symbolic references 符号引用和 static constants 静态常量.
其中静态常量不需要后续解析, 而符号引用需要进一步进行解析处理.
什么是静态常量, 什么是符号引用呢? 我们举个直观的例子.
String site="www.flydean.com"
上面的字符串 "www.flydean.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.flydean.com");
到底创建了多少个对象呢?
总结
class 文件中常量池保存的是字符串常量, 类和接口名字, 字段名, 和其他一些在 class 中引用的常量. 每个 class 都有一份.
运行时常量池保存的是从 class 文件常量池构建的静态常量引用和符号引用. 每个 class 都有一份.
字符串常量池保存的是 "字符" 的实例, 供运行时常量池引用.
运行时常量池是和 class 或者 interface 一一对应的, 那么如果一个 class 生成了两个实例对象, 这两个实例对象是共享一个运行时常量池还是分别生成两个不同的常量池呢? 欢迎小伙伴们留言讨论.
本文链接: http://www.flydean.com/jvm-run-time-constant-pool/
最通俗的解读, 最深刻的干货, 最简洁的教程, 众多你不知道的小技巧等你来发现!
来源: https://www.cnblogs.com/flydean/p/jvm-run-time-constant-pool.html