jvm中的栈简介
- 一、JVM体系结构
- 二、栈是什么?
- 三、栈的特性
- 四、栈帧
- 五、栈的运行原理
- 5.1 运行原理
- 5.2 代码示例
- 5.2.1 方法的入栈和出栈
- 5.2.2 没有捕获异常
- 5.2.3 捕获异常
- 六、栈帧的内部结构
- 七、运行时数据区,哪些部分存在Error和GC?
- 八、本文源码
一、JVM体系结构
二、栈是什么?
我们这次讲的是JVM中的栈,栈在JVM中的位置如图所示:
栈(stack)又名堆栈,它是一种运算受限的线性表。限定仅在表尾进行插入和删除操作的线性表。这一端被称为栈顶,相对地,把另一端称为栈底。向一个栈插入新元素又称作进栈、入栈或压栈,它是把新元素放到栈顶元素的上面,使之成为新的栈顶元素;从一个栈删除元素又称作出栈或退栈,它是把栈顶元素删除掉,使其相邻的元素成为新的栈顶元素。【百度百科】
关于栈的动画小视频可以更好的理解栈。秒懂百科:http://baike.baidu.com/l/m2YkZz0z
上述百度百科的概念比较抽象,可能比较难以理解。下面用大白话讲一下就能很好的理解了。
栈、栈顶、栈底、入栈、出栈,这几个概念要先有个印象。
你可以把栈当作是一个没有盖子的箱子,这个箱子就是栈,箱子的底部就叫作栈底,箱子口就叫作栈顶,往箱子里放东西就是压栈(PUSH),别名进栈、入栈,从箱子中拿出东西就是出栈(POP),别名退栈,这个东西在计算机中就叫作数据。只能对顶端进行操作,数据的添加和取出分别叫作入栈和出栈。
栈是一种数据结构,它是先进后出,后进先出的。有个专业术语叫FILO(First In Last Out)。
通常说到栈我们还会与另外一种数据结构进行比较“队列”,队列就是先进先出,专业术语叫FIFO(First In First Out)。
网上有个更形象的说法,很容易就让人记住了这两种数据结构:“喝多了吐就是栈,吃多了拉就是队列”。哈哈,大家自行体会和理解哈。
栈有时候又叫作栈内存,它主管程序的运行,生命周期和线程同步。一旦线程结束,栈就Over了!线程结束,栈内存也就释放了,所以对于栈来说,它是不存在垃圾回收的问题的。
三、栈的特性
栈是运行时的单位,而堆是存储的单位。
即:栈解决程序的运行问题,即程序如何执行,或者说如何处理数据。堆解决的是数据存储的问题,即数据怎么放,放在哪儿。
特性:
- 访问速度快,仅次于程序计数器
- 栈是线程私有的
- 存在OOM,不存在GC
- 栈存放的内容类型:8种数据类型 + 对象的引用 + 实例的方法。
即:
- Java虚拟机栈也是线程私有的,它的生命周期与线程相同(随线程而生,随线程而灭)。
- 如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError异常;如果虚拟机栈可以动态扩展,如果扩展时无法申请到足够的内存,就会抛出OutOfMemoryError异常;(当前大部分JVM都可以动态扩展,只不过JVM规范也允许固定长度的虚拟机栈)。
- Java虚拟机栈描述的是Java方法执行的内存模型:每个方法执行的同时会创建一个栈帧,一个方法就对应一个栈帧。对于我们来说,主要关注的stack栈内存,就是虚拟机栈中局部变量表部分。
四、栈帧
栈帧(Stack Frame)是用于支持虚拟机进行方法调用和方法执行的数据结构,它是虚拟机运行时数据区中的虚拟机栈(Virtual Machine Stack)的栈元素。
五、栈的运行原理
5.1 运行原理
- JVM直接对java栈的操作只有两个,就是对栈帧的压栈和出栈,遵循“先进先出”/“后进先出”原则。
- 在一条活动线程中,一个时间点上,只会有一个活动的栈帧。即只有当前真正执行的方法的栈帧(栈顶栈帧)是有效的,这个栈帧被称为当前栈帧,与当前栈帧相对应的方式就是当前方法,定义这个方法的类就是当前类。
- 执行引擎运行的索引字节码指令只针对当前栈帧进行操作。
- 如果该方法中调用了其他方法,对应的新的栈帧会被创建出来,放在栈的顶端,成为新的当前帧。
- Java方法有两种返回函数的方式,一种是正常的函数返回,使用return指令;另外一种就是抛出异常(没有try_catch)。不管使用哪种方式,都会导致栈帧被弹出。
5.2 代码示例
5.2.1 方法的入栈和出栈
5.2.2 没有捕获异常
5.2.3 捕获异常
六、栈帧的内部结构
每个栈帧的内部结构都是一样的,基本上都是包含如下信息:
- 局部变量表(Local Variables)
- 操作数栈(Operand Stack)(或表达式栈)
- 动态连接(Dynamic Linking)(或指向运行时常量池的方法引用)
- 方法返回地址(Return Address)(或方法正常退出或者异常退出的定义)
- 一些附件信息
我们在下图中只放大一个栈帧内容看:至于每个栈帧包含的内容可以自行百度了解。
七、运行时数据区,哪些部分存在Error和GC?
运行时数据区 | 是否存在Error | 是否存在GC | 是否线程私有 |
---|---|---|---|
方法区 | 是 | 是 | 否 |
堆 | 是 | 是 | 否 |
栈 | 是 | 否 | 是 |
程序计数器 | 否 | 否 | 是 |
本地方法栈 | 是 | 否 | 是 |
八、本文源码
没有异常的代码:
package com.iot.back.streamserver.controller;
/**
* <p>StackTest 此类用于:</p>
* <p>@author:hujm</p>
* <p>@date:2022年11月27日 22:12</p>
* <p>@remark:</p>
*/
public class StackTest {
public static void main(String[] args) {
StackTest stackTest = new StackTest();
// 执行方法A
stackTest.methodA();
}
public void methodA() {
System.out.println("方法methodA开始执行......");
methodB();
System.out.println("方法methodA执行结束......");
}
public int methodB() {
System.out.println("方法methodB开始执行......");
int b = 5;
int c = methodC();
System.out.println("方法methodB即将结束......");
return b + c;
}
public int methodC() {
System.out.println("方法methodC开始执行......");
int c = 8;
System.out.println("方法methodC即将结束......");
return c;
}
}
带有异常的代码:
package com.iot.back.streamserver.controller;
/**
* <p>StackTest 此类用于:</p>
* <p>@author:hujm</p>
* <p>@date:2022年11月27日 22:12</p>
* <p>@remark:</p>
*/
public class StackTest {
public static void main(String[] args) {
StackTest stackTest = new StackTest();
// 执行方法A
stackTest.methodA();
System.out.println("main方法正常结束!");
}
public void methodA() {
System.out.println("方法methodA开始执行......");
methodB();
System.out.println(1/0);
System.out.println("方法methodA执行结束=======");
}
public int methodB() {
System.out.println("方法methodB开始执行......");
int b = 5;
int c = methodC();
System.out.println("方法methodB即将结束......");
return b + c;
}
public int methodC() {
System.out.println("方法methodC开始执行......");
int c = 8;
System.out.println("方法methodC即将结束......");
return c;
}
}