以前学JVM的时候看过《深入理解JVM》,当时看的很模糊也记了些笔记,更像是为了应付面试。事实是确实把笔记都背上了,春招找实习的时候,内存管理、类加载、垃圾回收三连背一遍。后来自己做项目的时候,涉及到JVM的部分还是不怎么理解,最近重读了上面的书并且看了一些技术大佬的专栏,用博客记录下自己学习过程与思考。
本篇文章关注两个问题:
-
- Java字节码进入JVM后是怎么存储的?
为了解释上面问题,假设现在我们有一个Main类,调用compute方法执行计算操作,代码如下:
public class Math { public static final Integer CONSTANT = 10; public int compute() { int a = 1; int b = 2; int c = (a + b) * 10; return c; } public static void main(String[] args) { Math math1 = new Math(); Math math2 = new Math(); math1.compute(); math2.compute(); } }
对于第一个问题: Class文件是一组以8位字节位基础的单位的二进制流,下图就是显示了如何生成字节码文件。
使用Sublime Text查看Math.class,图片只截取了部分,编辑器是使用16进制显示的。为了方便查看,我们使用 javap -c 指令对代码进行反汇编,就可以得得到可读性更强的文件。
那么Class文件被加载后在JVM中是如何存储的呢?我们以 HotSpot VM为例,这是目前使用最广泛的Java虚拟机。虚拟机主要由类装载子系统、运行时数据区和执行引擎三部分组成。JVM内存模型将运行时数据区分为五个部分,下面图中其中紫色部分是线程私有的,黄色是线程公有的。整个代码的执行流程在JVM内存中是这样的:
- 局部变量表:存放的是方法在执行时各种基本类型和引用类型变量,以及returnAddress类型(指向了一条字节码指令的地址);
- 方法出口:保存的是方法执行完后回到主线程的哪个位置。对于main栈帧,局部变量表里的math变量存放的是堆内存中math变量的地址。
- 操作数栈:临时存放方法执行时的变量
- 动态链接:Class 文件中存放了大量的符号引用,这些符号引用指向的是方法。程序运行期间调用方法时,根据运行时常量池的参数,静态符号引用变成直接引用;对象头里的指针会动态的找到方法区中存储的调用方法的信息。
程序计数器:记录的是字节码指令正在执行或者即将执行的行号,比如这行 ”0: iconst_1“执行完了,程序计数器值就是1,表示即将执行下一行指令。
本地方法栈:作用和虚拟机栈类似,为native修饰的方法服务。
方法区:JDK1.8及以后称为元空间,存储被虚拟机加载的类信息、常量、静态变量等。1.8以后方法区使用的是本机的内存。例如 这一行指令 ”public static final java.lang.Integer CONSTANT;“就是静态常量 CONSTANT的信息。
堆:堆是JVM内存模型中最大的一块,虚拟机启动时就会创建,存储的是大部分对象。
参考资料:《深入理解Java虚拟机》第二版 周志朋
《深入拆解Java虚拟机》郑雨迪
《JVM虚拟机底层原理分析与性能调优》程序员诸葛