JVM扩展(3):常量池引发的一连串思考(下)
2021-05-10 本文已影响0人
_River_
百度百科:
https://baike.baidu.com/item/%E5%B8%B8%E9%87%8F%E6%B1%A0/3855836?fr=aladdin
先暂时认为:JDK1.8 的所有类型常量池都存储在元空间(方法区) (后续会有解释)
目录
1:源码的编译(包括类加载过程)
2:静态常量池(类常量池)(类文件常量池)(class constant pool) (编译期)
3:全局常量池(String pool)(类加载过程中)
4:运行时常量池(runtime constant pool)(类加载完成后)
5:string的实例对象 讲解
6:常量池的逻辑存储与物理存储
7:常量池存储位置的 结论
8:静态常量池 全局常量池 运行时常量池 的关系
1:源码的编译(包括类加载过程)
1:Java源代码
2:经过JDK中Javac 编译
3:字节码文件(.class)
4:JVM 类加载器 进行加载字节码文件
5:方法:解释:
1:解释器 逐行解释执行
2:机器可执行的二进制机器码
5:方法:编译:
后续引进:JIT即时编译器(Just In Time Compiler)
处理热点代码(一次编译 多次执行)
2:JIT 运行时进行编译
3:机器可执行的二进制机器码
4:编译完成后机器码保留
5:机器码下次可以直接使用
类加载过程:
其中loadClass的类加载过程有如下几步:
加载 >> 验证 >> 准备 >> 解析 >> 初始化
2:静态常量池(类常量池)(类文件常量池)(class constant pool) (编译期)
java文件被编译成class文件之后,就是会生成类文件,也就是会生成里面的类常量池。
类文件包含两大部分
1:类型信息:类的版本、字段、方法、接口等描述信息外
2:静态常量池(class constant pool ),
1:各种字面量(Literal)
2:符号引用(Symbolic References)。
字面量:常量等
符号引用:与直接引用(指针)区分 ,同样的也可以无歧义的定位到对应目标。
3:全局常量池(String pool)(类加载过程中)
全局字符串池里的内容是在类加载过程中,经过验证,准备阶段之后在堆中生成字符串对象实例,
然后将该字符串对象实例的引用值存到string pool中。(存放的是地址 而 不是对象本身)
HotSpot VM里实现的string pool功能的是一个StringTable类,
它是一个哈希表这个StringTable在每个HotSpot VM的实例只有一份,被所有的类共享。
4:运行时常量池(runtime constant pool)(类加载完成后)
JVM在执行某个类的时候,必须经过首次类加载。
而当首次类加载把类到内存中后,jvm就会将 类常量池中的内容 存放到运行时常量池中,由此可知,运行时常量池也是每个类都有一个。
由于静态常量池中存的是对象的符号引用。
1:类加载过程中的解析过程 :使用类常量池中的符号引用 查询 全局常量池
2:把符号引用 替换成 直接引用(指针)
3:把类常量池的内存 全部存放到 运算时常量池
4:保证运行时常量池所引用的字符串与全局字符串池中所引用的是一致的。
5:string的实例对象 讲解
public class HelloWorld {
public static void main(String []args) {
String str1 = "hesuijin";
String str2 = "hesuijin";
String str3 = new String("HSJ");
String str4 = str3.intern();
String str5 = "HSJ";
System.out.println(str1 == str2);//true
System.out.println(str3 == str4);//false
System.out.println(str4 == str5);//true
}
}
引用值 = 引用地址 = 地址 = 指针
1:执行第一句:堆中会有一个”hesuijin”实例,全局常量池 中存放着”hesuijin”的一个引用值
2:执行第二句: 解析str3的时候查找全局常量池,里面有”hesuijin”的引用值,
所以str3的引用值与之前的那个已存在的相同;
3:执行第三句:时候会生成两个实例(两者不一样):
1:堆中会有一个”HSJ”实例,全局常量池中存放着”HSJ”的一个引用值
2:一个是New出来的一个”HSJ”的实例对象 (正常对象)
最后str3会使用默认New出来的实例对象的引用值
4:执行第四句:intern()函数 如果存在 全局常量池中 str3 的引用值 则返回 ,
如果没有就将New出来的实例对象的引用值添加进去;
现在存在 因此返回 全局常量池中”HSJ”的引用值
5:执行第五句:解析str5的时候查找全局常量池,里面有”HSJ”的引用值,
所以str5的引用值与之前的那个已存在的相同;
因此:str1(全局常量池中) str2 (全局常量池中) 一致
str3(New出来的对象) str4 (全局常量池中) 不一致
str4(全局常量池中) str5 (全局常量池中) 一致
6:常量池的逻辑存储与物理存储
取消永久代后,使用元空间来实现方法区。
在JDK1.8中,把JDK 7中永久代还剩余的内容(主要是类型信息)全部移到元空间中。
注意这里的剩余内容:说明原来移除从永久代移出的字符串常量池,静态常量池,运行时常量池 ,
在更换了方法区实现后,并没有顺势进入到元空间,那么它们到哪里去了呢?
在JDK1.8中,使用元空间代替永久代来实现方法区,但是方法区并没有改变,
所谓"Your father will always be your father",变动的只是方法区中内容的物理存放位置。
正如上面所说,类型信息(元数据信息)等其他信息被移动到了元空间中;
但是常量池(静态常量池 运行时常量池)和字符串常量池被移动到了堆中。
但是不论它们物理上如何存放,逻辑上还是属于方法区(元空间)。
但是不论它们物理上如何存放,逻辑上还是属于方法区(元空间)。
但是不论它们物理上如何存放,逻辑上还是属于方法区(元空间)。
7:常量池存储位置的 结论
那么现在我们可以看到有三种常量池:
静态常量池(类常量池)(类文件常量池)(class constant pool)
运行时常量池(runtime constant pool)
字符串常量池(全局常量池)(String pool)
考虑其物理存储位置 应该是存放在堆中
但考虑其逻辑存储位置 应该是存放在元空间(方法区)中
那么在绝大多数的网上说法 都是指
堆存放的是 对象 数组引用类型 非静态变量 String(物理存储)
方法区: 静态变量、常量, 静态和非静态的方法 类信息 String(逻辑存储)
那么现在我们先以逻辑存储位置为准:
JDK1.8 的所有类型常量池都存储在元空间(方法区)
那么现在我们先以逻辑存储位置为准:
JDK1.8 的所有类型常量池都存储在元空间(方法区)
那么现在我们先以逻辑存储位置为准:
JDK1.8 的所有类型常量池都存储在元空间(方法区)
8:静态常量池 全局常量池 运行时常量池 的关系
所有的常量池都存储在元空间(方法区)
1:编译期:静态常量池(类常量池)(类文件常量池)(class constant pool)
2:类加载期:全局常量池(String pool)
3:类加载完成期:运行时常量池(runtime constant pool)
1:编译期 产生静态常量池 存放
1:各种字面量(Literal)
2:符号引用(Symbolic References)。
2:在类加载期 如果产生 字符串对象实例
1:String str1 = “hesuijin” 产生一个实例
那么会将str1实例对象地址存放到全局常量池
2:String str2 = new String(“HSJ”) 产生两个实例
1:那么会将str2 其中一个实例对象地址存放到全局常量池
2:那么会将 new 出来的一个实例对象 当成正常对象处理
其str2 地址指向 new出来 的一个实例对象
3:类加载完成后
1:静态常量池的 各种字面量(Literal) 会复制一份到 运行时常量池
2:静态常量池的 符号引用(Symbolic References) 经过 全局常量池转换后 会复制一份到 运行时常量池
3:通过其他途径创建的常量 :如基本类型的包装类 如String 也会加入到 运行时常量池