JVM底层原理
- JDK/JRE
- 1JDK
Java developer Kit
编译java 到class
- 2 JRE
Java runtime Environment 对就不同系统,只需要安装对应的JRE版本,即可运行(跨平台),一次编译任何处运行。
2. JVM结构分析
认识JVM ,怎么管理内存
2.1 类加载子系统
- 加载
加载是类装载的第一步,首先通过class文件的路径读取到二进制流,并解析二进制流将里面的元数据(类型、常量等)载入到方法区,在java堆中生成对应的java.lang.Class对象。
- 连接
连接过程又分为3步,验证、准备、解析
验证
验证的主要目的就是判断class文件的合法性,比如class文件一定是以0xCAFEBABE开头的,另外对版本号也会做验证,例如如果使用java1.8编译后的class文件要再java1.6虚拟机上运行,因为版本问题就会验证不通过。
除此之外还会对元数据、字节码进行验证,具体的验证过程就复杂的多了,可以专门查看相关资料去了解。
准备
准备过程就是分配内存,给类的一些字段设置初始值,例如:
public static int v=1;
这段代码在准备阶段v的值就会被初始化为0,只有到后面类初始化阶段时才会被设置为1。
但是对于static final(常量),在准备阶段就会被设置成指定的值,例如:
public static final int v=1;
这段代码在准备阶段v的值就是1。
解析
解析过程就是将符号引用替换为直接引用,例如某个类继承java.lang.object,原来的符号引用记录的是“java.lang.object”这个符号,凭借这个符号并不能找到java.lang.object这个对象在哪里?而直接引用就是要找到java.lang.object所在的内存地址,建立直接引用关系,这样就方便查询到具体对象。
- 初始化
始化过程,主要包括执行类构造方法、static变量赋值语句,staic{}语句块,
需要注意的是如果一个子类进行初始化,那么它会事先初始化其父类,保证父类在子类之前被初始化。所以其实在java中初始化一个类,那么必然是先初始化java.lang.Object,因为所有的java类都继承自java.lang.Object。
2.2 运行时数据区(Runtime Data Areas)
是什么?有什么用?怎么用?
抽象:数据、指令、控制 层面分析运行时数据区
运行时数据区:
2.2.1方法区
方法区:是系统分配的一个内存逻辑区域,是JVM在装载类文件时,用于存储类型信息的(类的描述信息)。存储类信息、常量、静态变量、JIT
- 类的基本信息: XXX.class
- 每个类的全限定名
- 每个类的直接超类的全限定名(可约束类型转换)
- 该类是类还是接口
- 该类型的访问修饰符
- 直接超接口的全限定名的有序列表
- 已装载类的详细信息:
- 运行时常量池(在方法区中,每个类型都对应一个常量池,存放该类型所用到的所有常量,常量池中存储了诸如文字字符串、final变量值、类名和方法名常量。它们以数组形式通过索引被访问,是外部调用与类联系及类型对象化的桥梁。(存的可能是个普通的字符串,然后经过常量池解析,则变成指向某个类的引用))
- 字段信息:字段信息存放类中声明的每一个字段的信息,包括字段的名、类型、修饰符
- 方法信息:类中声明的每一个方法的信息,包括方法名、返回值类型、参数类型、修饰符、异常、方法的字节码
2.2.2堆(实例对象)
2.2.3程序计数器
指向当前线程正在执行字节码的指令的地址 行号 ?为什么
(指令在CPU上运行、时间片区执行)
线程独享
2.2.4虚拟机栈(Method)
虚拟机栈:(数据结构)存储当前线程运行方法所需要的方法、指令、返回地址
记录:
(线程只是执行单位)
First in /Last Out 先进后出
栈桢:
javap 输出 结构
动态链接:service.do() ,调用实例接口类方法
2.2.5本地方法栈
原理 与虚拟机栈类似,类比方法
本地native方法
底层实现:C/C++
2.3. 执行引擎
执行包在装载类的方法中的指令,也就是方法
以下面代码为例看一下执行引擎是如何将一段代码在执行部件上执行的,如下一段代码:
public class Math{
public static void main(String[] args){
int a = 1 ;
int b = 2;
int c = (a+b)*10;
}
}
其中main的字节码指令如下:
偏移量 指令 说明
0: iconst_1 常数1入栈
1: istore_1 将栈顶元素移入本地变量1存储
2: iconst_2 常数2入栈
3: istore_2 将栈顶元素移入本地变量2存储
4: iload_1 本地变量1入栈
5: iload_2 本地变量2入栈
6: iadd 弹出栈顶两个元素相加
7: bipush 10 将10入栈
9: imul 栈顶两个元素相乘
10: istore_3 栈顶元素移入本地变量3存储
11: return 返回
对应到执行引擎的各执行部件如图:
在开始执行方法之前,PC寄存器存储的指针是第1条指令的地址,局部变量区和操作栈都没有数据。从第1条到第4条指令分别将a、b两个本地变量赋值,对应到局部变量区就是1和2分别存储常数1和2,如图:
第5条和第6条指令分别是将两个局部变量入栈,然后相加,如图:
1先入栈2后入栈,栈顶元素是2,第7条指令是将栈顶的两个元素弹出后相加,结果再入栈,如图:
可以看出,变量a和b相加的结果3存在当前栈顶中,接下来第8条指令将10入栈,如图:
当前PC寄存器执行的地址是9,下一个操作是将当前栈的两个操作数弹出进行相乘并把结果压入栈中,如图:
第10条指令是将当前的栈顶元素存入局部变量3中,如图:
第10条指令执行完后栈中元素出栈,出栈的元素存储在局部变量区3中,对应的是变量c的值。最后一条指令是return,这条指令执行完后当前的这个方法对应的这些部件会被JVM回收,局部变量区的所有值将全部释放,PC寄存器会被销毁,在Java栈中与这个方法对应的栈帧将消失。