JVM学习笔记之三
知识点和问题
1.虚拟机栈出现的背景
由于跨平台性的设计,Java的指令都是根据栈来设计的,不同平台CPU结构不同,所以不能设计为基于寄存器的。
优点是跨平台,指令集小,编译器容易实现,缺点是性能下降,实现同样的功能需要更多的指令。
2.内存中的栈和堆解决的问题有哪些?
栈是运行时的单位,而堆是存储的单位。
栈解决程序的运行问题,即程序如何执行,或者说如何处理数据。堆解决的是数据存储的问题,即数据怎么放、放在哪儿。
3.什么是Java虚拟机栈?
Java虚拟机栈(Java Virtual Machine Stack),早期也叫Java栈,每个线程在创建时都会创建一个虚拟机栈,其内部保存一个一个的栈帧(Stack Frame),对应着一次次的Java方法调用。
是线程私有的
声明周期和线程一致
作用:主管Java程序的运行,它保存方法的局部变量、部分结果,并参与方法的调用和返回。
4.虚拟机栈的优点
栈是一种快速有效的分配存储方式,访问速度仅次于程序计数器
JVM直接对Java栈的操作只有两个:
1)每个方法执行,伴随着进栈(入栈、压栈)
2)执行结束后的出栈工作
对于栈来说不存在垃圾回收问题,存在内存溢出问题
5.开发中遇到的常见的异常有哪些?
实际开发中框架上遇到的异常
JVM中会出现的异常
1)栈异常(*Error溢出异常、OutOfMemoryError动态扩展内存异常)
样例:
public class StackErrorTest {
public static void main(String[] args){
main(args);
}
}
测试结果:
Exception in thread "main" java.lang.*Error
at com.atguigu.java.StackErrorTest.main(StackErrorTest.java:5)
at com.atguigu.java.StackErrorTest.main(StackErrorTest.java:5)
6.设置栈内存大小
https://docs.orcale.com/en/java/javase/11/tools/tools-and-command-reference.html
可以使用参数-Xss选项来设置线程的最大栈空间,栈的大小直接决定了函数调用的最大可达深度。
在IntelliJ IDEA中的设置方法:
Run->Edit Configurations->Application->StackErrorTest(测试类名)->VM Option
7.栈中存储什么?
每个线程都有自己的栈,栈中的数据都是以栈帧(Stack Frame)的格式存在。
在这个线程上正在执行的每个方法都各自对应一个栈帧(Stack Frame)
栈帧是一个内存区块,是一个数据集,维系着方法执行过程中的各种数据信息
1)JVM直接对Java栈的操作只有两个,就是对栈帧的压栈和出栈,遵循"先进后出"/"后进先出"的原则。
2)在一条活动线程中,一个时间点上,只会有一个活动的栈帧,即只有当前正在执行的方法的栈帧(栈顶栈帧)是有效的,这个栈帧被称为当前栈帧(Current Frame),与当前栈帧相对应的方法就是当前方法(Current Method),定义这个方法的类就是当前类(Current Class)。
3)执行引擎运行的所有字节码指令只针对当前栈帧进行操作
4)如果在该方法中调用了其他方法,对应的新的栈就会被创建出来,放在栈的顶端,成为新的当前栈
5)不同线程中所包含的栈帧是不允许存在相互引用的,即不可能在一个栈帧之中引用另外一个线程的栈帧。
6)如果当前方法调用了其他方法方法,返回之际,当前栈帧会传回此方法的执行结果给前一个栈帧,接着虚拟机会丢弃当前栈帧,使得前一个栈帧重新成为当前栈帧。
7)java方法有两种返回函数的方式,一种是正常的函数返回使用return指令,另外一种是抛出异常,不管使用哪种方式都会导致栈帧被弹出。
8.栈帧的内部结构
局部变量表(Local Variables)
操作数栈(Operand Stack)
动态链接(Dynamic Linking)(或指向运行时常量池的方法引用)
方法返回地址(Return Address)(或方法正常退出或者异常退出的定义)
一些附加信息
9.局部变量表
1)局部变量表也可以称之为局部变量数组或本地变量表
2)定义为一个数字数组,主要用于存储方法参数和定义在方法内的局部变量,这些数据类型包括各类基本数据类型、对象引用(reference),以及returnAdderss类型
3)由于局部变量表是建立在线程的栈上,是线程的私有数据,因此不存在数据安全问题
4)局部变量表的容量大小是在编译期确定下来的,并保存在方法的Code属性的maximum local variables数据项中,在方法运行期间是不会改变局部变量表的大小的
5)局部变量表中的变量只在当前方法调用中有效,在方法执行时,虚拟机通过使用局部变量表完成参数值到参数变量列表的传递过程,当方法调用结束后,随着方法栈桢的销毁,局部变量表也随之销毁。
10.关于Slot的理解
参数值的存放总是在局部变量数组的index0开始,到数组长度-1的索引结束。
局部变量表,最基本的存储存储单元是Slot(变量槽)
局部变量表中存放编译器可知的各种基本数据类型(8种),引用数据类型(reference),returnAddress类型的变量
在局部变量表里,32位以内的类型只能占用一个slot(报错returnAddress类型),64位的类型(long和double)占用两个slot.
1)byte、short、char在存储前被转换为int,boolean也被转换位int,0表示false,非0表示true
2)long和double则占据两个Slot
栈帧中的局部变量表中的槽位是可以重复用的,如果一个局部变量过了其作用域,那么其在其作用域之后申明的新的局部变量就很有可能会复用过去局部变量的槽位,从而达到节省资源的目的。
在栈帧中,与性能调优关系最为密切的部分就是前面提到的局部变量表,在方法执行时,虚拟机使用局部变量表完成方法的传递。
局部变量表中的变量也是重要的垃圾回收根节点,只要被局部变量表中直接或者间接引用的对象都不会被回收。
11.操作数栈是用数组来实现的
每一个独立的栈帧中除了包含局部变量以外,还包含一个后进先出(Last-In-First-Out)的操作数栈,也可以称之为表达式栈(Expression Stack)。
操作数栈在方法执行过程中,根据字节码指令,往栈中写入数据或提取数据,即入栈和出栈。
1)操作数栈主要用于保存计算过程中的中间结果,同时作为计算过程中变量临时的存储空间。
2)操作数栈就是jvm执行引擎的一个工作区,当一个方法刚开始执行的时候,一个新的栈帧也会随之被创建出来,这个方法的操作数栈是空的。
3)每一个操作数栈都会拥有一个明确的栈深度并用于存储数值,其所需的最大深度的编译期就定好了,保存在方法的Code属性中,为max_stack的值。
4)栈中的任何一个元素都可以包含任意的Java数据类型。
32比特的类型占用一个站单位深度
64比特的类型,占用两个占单位。
5)操作数栈并非采用访问索引的方式进行数据访问的,而是只能通过标准的入栈(push)和出栈(pop)来完成一次数据访问。
6)如果被调用的方法带有返回值的话,其返回值将会被压入当前栈帧的操作数栈中,并跟新PC寄存器中下一条需要执行的字节码指令。
7)操作数栈中元素的数据类型必须与字节码指令的序列严格匹配,这由编译器在编译期间进行校验,同时在类加载过程中类检验阶段的数据流分析阶段需要再次进行校验。
8)Java虚拟机的解释引擎是基于栈的执行引擎,其中的栈指的就是操作数栈。
上一篇: python:等考之三
下一篇: PyODPS的使用