JVM内存模型
相信有很多人面试的时候都会被问到有关于JVM的问题,我相信很多大牛都可以很轻松的回答出来,但是也有很多的人(包括我)只懂其表不懂其里,因此通过这篇文章可以令大家对JVM有个认识。
首先看一下JVM的内存模型:
这图大家应该很熟悉,后面我会一一介绍他们之间相互的作用,先把JVMTest的代码奉上:
public class JVMTest {
public int compute(){
int a = 1;
int b = 2;
int c = (a + b) * 10;
return c;
}
public static void main(String[] args) {
JVMTest jvmTest = new JVMTest();
jvmTest.compute();
System.out.println(123);
}
}
代码没什么特别,就是简单的逻辑运算。都准备好了就开始正事。
线程栈
在我们执行main方法的时候,JAVA虚拟机会分配一部分内存区域用于存储线程独享的局部变量,而这部分内存区域就是JVM模型中的线程栈。如下图所示:
线程栈由多个栈帧组成,什么是栈帧?就是当一执行某个方法时,就会马上在线程栈中分配一块内存区域,用于存放这个方法的局部变量。如上面的代码,当我们执行main方法时就会在线程栈中压入一个main方法的栈帧,如下图所示:
当执行main方法的时候,会执行jvmTest.compute()方法,这时候就会压入一个compute()方法的栈帧,如图所示:
到这里,大家都应该明白为什么要用栈的结构来存放线程局部变量,当compute()方法执行完时,就会立刻出栈,马上释放资源,然后回到main()方法中继续执行,当main()方法执行完,就会出栈释放内存。先调用的方法最后销毁,栈的特性非常符合这一行为。
栈帧主要包括下面四个组成部分:
- 局部变量
- 动态链接
- 操作数栈
- 方法出口
要讲述这几个部分还是要结合代码看看,我们用javap -c看一下编译后的字节码,我这里主要看compute()方法:
见到这些代码不用怕,网上都有相关翻译,我这里简单说下:
- iconst_1将int类型常量1压入操作数栈。
- istore_1将int类型值值存入变量1。
如果这里看不懂的话可以结合下面图解:
变量b同理,下面我们看看变量c的代码:
int c = (a + b) * 10;
再看看编译后的代码:
步骤如下:
- iload_1表将局部变量1的值装载到操作数栈中,iload_2同理
- iadd表示将int类型相加,将操作数栈中最新的两个元素弹出,做加法后将结果重新载入到操作数栈中。
- bipush 10 将常量10压入操作数栈中。
- imul将int类型相乘,将操作数栈中最新的两个元素弹出,做乘法后将结果重新载入到操作数栈中。
- istore_3将int类型值值存入变量3中。
- iload_3将局部变量3的值装载到操作数栈中。
- ireturn将局部变量3的值返回。
方法出口里存储一个值,主要是用于执行完方法后所返回的代码位置。如上诉main方法,当调用完jvmTest.compute()后,通过方法出口的这个值可以大概知道执行到main方法的哪一行代码。
堆
new出来的对象都存放于堆中,如上述代码:
JVMTest jvmTest = new JVMTest();
new JVMTest()的对象存放于堆中并且有一个特定的指针地址,而main方法的局部变量jvmTest的值则是这个地址的值。因此堆和栈的关系逐渐明了。
结尾
JVM的内存模型基本上是面试的必考题,除了应付面试,我们更应该是为了认识它和熟悉它,而不是面试完就忘记啥是栈啥是堆。不要抱着因为用不到而不学的心态,一个程序员优秀程度不取决于你会用多高级的技术,而是取决于你对原理的理解程度。