对于开发人员来说,如果不了解Java的JVM,那真的是很难写得一手好代码,很难查得一手好bug,同时,JVM也是面试环节重点考察点,下面我们来了解一下JVM内部结构!
jvm内存关系如图所示:
上边的图会结合Math类进行分析,Math如下所示
//Math类
public class Math {
public static final int initData = 666;
public static User user = new User();
public int compute() {
int a = 1;
int b = 2;
int c = (a + b) * 10;
return c;
}
public static void main(String[] args) {
Math math = new Math();
math.compute();
}
}
1.1 Math中的main方法执行流程
①:首先Math.class由应用类加载器加载到jvm内存中。
②:在线程执行Math中的main方法时,会在jvm整个栈内存中会给当前线程分配一块内存作为当前的线程栈 空间。
③:main()方法只要一开始运行,会在当前线程的栈空间内部压入main()方法的栈帧,栈帧内用来存放main方法的局部变量,比如 math,在main方法的栈帧中存储的是Math对象的对象头地址,用来指向堆空间中Math对象的位置。
④:然后开始执行compute()方法,会在当前线程的栈空间内,压入一个compute()的栈帧(每一个方法对应一个栈帧),栈帧内部包含局部变量表、操作数栈、动态链接、方法出口等。
⑤:等compute()方法执行完毕,释放栈空间,继续执行main()方法
结论:
- 线程栈中每一个方法对应一个栈帧,保证每个方法内部的局部变量相互隔离。
- 栈帧的执行遵循先进后出原则,即先压入的main方法后执行,后压入的compute方法先执行。
1.2 线程栈内部结构解析
局部变量表:存储局部变量,如a=1,b=2,或者对象。
操作数栈:也是一个栈,用来临时存放局部变量表中的局部变量的结果值,例如执行compute方法时,先把a的值放入局部变量表,再把a的值 1 放入操作数栈,然后再把操作数栈的值1,赋给局部变量表中的a,包括 (a + b) * 10的计算也在操作数栈中进行。
动态链接:把符号引用转换为直接引用,比如执行到compute()方法时,会把这个方法的符号转换为方法区的地址。
方法出口:方法执行完毕后,回到哪一行,哪个方法的哪个位置,是方法出口来记录的。
1.3 什么是程序计数器,为什么每个线程都要包含程序计数器?
①:程序计数器是记录当前线程的执行字节码的位置。
②:字节码执行引擎在执行方法区中的字节码信息时,会记录当前执行的行号,每当线程往下执行一步,就会改变程序计数器的行号。
③:在多线程模式下,如果当前线程的cpu时间片被别的线程抢去,等他再抢回cpu时,需要记录上一步执行的位置,否则要从头开始执行,而程序计数器就提供了当前执行的位置信息,所以每个线程都有自己的程序计数器。
1.4 本地方法栈
因为java的底层是C++,如果线程在执行过程中调用了很底层的C++方法(比如Thread.start