本地方法栈:和虚拟机栈类似,只是它描述的是为虚拟机是用到的Native方法出栈和入栈的过程(通常我们不需要了解这块,它底层是C语言实现的)。
虚拟机栈:描述的是线程进栈出栈的过程,线程结束内存自动释放。它用来存储当前线程运行方法所需要的数据、指令、返回地址。(即局部变量和正在调用的方法) 方法被调用时会在栈中开辟一块叫栈帧的空间,方法运行在栈帧空间中。栈帧出栈后,里面的局部变量直接就从内存里清理掉了。
先进后出
// main方法先入栈,然后程序从上往下执行,a 和 b 相继入栈,程序执行完之后出栈,可以看到main方法最后执行完,所以栈是先进后出的。public classTest {public static voidmain(String[] args) {
a();
System.out.println("main...");
}public static voida(){
b();
System.out.println("a...");
}public static voidb(){
System.out.println("b...");
}
}========== 控制台输出 ===========b...
a...
main...
栈帧的逻辑内存图里面包含局部变量表,操作数栈,动态链接,出口...(注意:栈帧里面包含但不仅仅只有这些, 程序员开发主要是关注这些 )。
public static voiddemo(){/*** 【局部变量表】 存放该方法的参数变量,和方法内部定义的局部变量,若该变量为引用类型,则存的该变量的引用地址。在java文件编译成.class文件的时候,这个表的容量最大值就确定下来了。*/String a= "局部变量a";
String b= "局部变量b";int c = 2,d = 3; //局部变量c和d
/*** 【操作数栈】 也称为表达式栈,通过字节码指定把值压入栈定,稍后另一个指令就可以弹出这个值使用。*/
int e = c*d; //操作数栈
/*** 【动态链接】 也称为常量池。用来保存常量值和符号引用。符号引用:A方法在运行时调用B方法,就是通过B方法的符号引用去找到B的内存地址.)*/UserDao dao= newUserDao();
dao.insert(a);/*** (出口):描述的就是出栈的过程。方法的返回有两种情况。(正常退出时:根据方法的定义来决定是否要传返回值给上层调用者。异常退出时:则是需要通过异常处理来确定。无论通过哪种方式退出,都会跳到当前方法被调用的位子。*/}
* 如果线程请求的栈深度⼤大于虚拟机所允许的深度,将抛出 StackOverflowError 异常
代码运行流程
比如现在有这么一段代码
1. 首先main线程会来执行User类的main方法。main线程会有自己的一个虚拟机栈,他会把main方法的栈帧压入虚拟机栈中。
2. 然后main()方法里面又调用了loadUserId()方法,此时就会创建一个loadUserId()方法的栈帧,并压入main线程的虚拟机栈中。
3. 此时loadUserId()方法中,有一个局部变量 order
4. 然后创建了一个 Order 类的实例对象,此时就会在java堆内存中分配这个实例对象的内存空间。并且order变量会指向Order实例对象的内存空间
5. 最后就会通过局部变量 order 引用Order的实例对象去执行它的getUserId()方法了