文章目录
2.1 JVM架构图
JVM主要分为五大模块:类加载器子系统、运行时数据区、执行引擎、本地方法接口和垃圾收集模块。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8Urjx9Uw-1582538218972)(D:\PersonalNote\JVM\images\JVM架构.png)]
2.2 运行时数据区和线程
2.2.1 JVM系统线程
在HotSpot虚拟机中,每个线程都与操作系统的本地线程直接映射(可以理解为java线程的执行是调用本地线程完成的)
- 当一个Java线程准备好执行以后,此时一个操作系统的本地线程也会同时创建,Java线程执行终止后,本地线程也会回收
在HotSpot虚拟机的后台有很多线程(不包括main线程以及这个main线程自己创建的线程)
- 虚拟机线程:需要到达安全点才会出现,这种线程的执行类型包括垃圾收集,线程栈收集,线程挂起以及偏向锁撤销
- 周期任务线程:一般用于周期性操作的调度执行
- GC线程:对不同种类的垃圾收集行为提供支持
- 编译线程:这种线程在运行时会将字节码编译成本地代码
- 信号调度线程:这种线程接受信号并发给JVM,在他内部通过调用适当的方法进行处理
2.2.2 运行时数据区
运行时数据区包括:方法区、堆、虚拟机栈(栈)、程序计数器、本地方法栈,前两者为所有线程共享的数据区生命周期同JVM,后三者为线程独有的数据区随着线程而生灭
2.2.3 程序计数寄存器
1.概述
JVM中的程序计数寄存器(Program Counter Register)是对物理PC寄存器的一种抽象模拟,它是软件层面的组件而非物理存在的。
2.作用
用来存储指向下一条指令的地址(也就是即将要执行的指令代码),执行引擎根据PC寄存器存储的地址来读取下一条指令。字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令
在虚拟机栈中,程序计数器会存储当前线程正在执行的Java方法的JVM指令地址,在本地方法栈中,则是未指定值(undefined)
它是唯一一个在Java虚拟机规范中没有规定任何OutOfMemoryError情况的区域
2.2.4 虚拟机栈(栈)
1.概述
每个线程都会创建一个虚拟机栈,其内部保存一个个的栈帧(Stack Frame),对应着一次次的方法调用(即使是同一个方法调用两次会也产生两个栈帧)。线程私有,生命周期同线程一致。
在一条活动线程上,一个时间点上只会有一个活动的栈帧,即当前正在执行方法的栈帧(栈顶栈帧)是有效的,称为当前栈帧,与当前栈帧对应的方法就是当前方法,定义这个方法的类就是当前类。
如果当前方法内调用了新的方法,对应的新栈帧创建并放入栈顶端称为新的当前栈帧
2.作用
主管Java程序的运行(即方法的调用),它保存方法的局部变量、部分结果,并参与方法的调用和返回
特点(优点):
- 访问速度快,仅次于程序计数器
- JVM对虚拟机栈的操作只要两个:方法执行入栈,执行结束出栈
- 对于栈来说不存在垃圾回收问题
栈是运行时的单位,而堆是存储的单位:栈解决程序的运行问题,即程序如何运行、如何处理数据。堆解决的是数据存储的问题,即数据怎么存、存在哪。
栈中可能出现的异常:
- 如果采用固定大小的虚拟机栈,在线程请求分配的栈容量超过虚拟机允许的最大容量时,虚拟机会抛出StackOverflowError异常
- 如果虚拟机栈可以动态扩展,在尝试扩展是无法申请到足够的内存,或者在创建新线程的时候没有足够内存创建对应的虚拟机栈没那么虚拟机会抛出OutOfMemoryError异常
可以使用-Xss参数设置栈的最大空间(不加单位默认为bytes,可以加KB、MB),默认大小为1024KB
3.栈帧
每个栈帧中存储着:
- 局部变量表(Local Variables)
- 操作数栈(Operand Stack)(也称表达式栈)
- 动态链接(Dynamic Linking)(也称指向运行时常量池的方法引用)
- 方法返回地址(Return Address)(也称方法正常退出或者异常退出的定义)
- 一些附加信息
3.1 局部变量表
-
也称为局部变量数组或本地变量表,它是一个一维数字数组,主要用于存放方法参数(形参)和定义在方法体内的局部变量。这些数据类型包括各类基本数据类型、对象引用以及返回值类型。
-
由于局部变量表是线程私有的,因此不存在数据安全问题
-
局部变量表所需要的容量大小是在编译阶段确定的,并保存在方法的Code属性的maximum local variables数据项中,在方法运行期间不会改变局部变量表的大小
-
局部变量表中的变量只在当前方法中生效(当方法执行结束,随着栈帧销毁局部变量表也被销毁)
-
局部变量表的大小影响着栈帧的大小(局部变量表空间占比较大)
-
局部变量表中的变量也是重要的GC Root,主要被局部变量表中直接或间接引用的对象都不会被回收
-
局部变量表的基本单位是Slot(槽),在局部变量表中:
- 32位以内的类型值占一个slot(包括返回值(returnAddress)类型、float类型。byte、short、char、boolean在存储前会被转换为int)
- 64位的类型(long和double)占两个slot
- JVM会为每一个slot分配一个访问索引,通过这个索引即可访问局部变量的值
- 当一个方法被调用时(创建栈帧),他的方法参数和局部变量会按照顺序复制到局部变量表中
- 如果要访问一个64位的变量值,只需要使用起始索引即可(比如一个double变量占了4、5两个slot,那么访问他是用4这个索引)
- 如果当前方法是由构造方法或者实例方法(非静态方法)创建的,那么所属对象的引用this将会放在index为0的slot处,其余参数顺序排列。
- slot可以重复利用,如果一个局部变量过了其作用域,那么在其作用域之后申明的新的局部变量就很有可能会重复用过期的slot,从而达到节省资源的目的
public void test(){ int a = 0; { int b = 0; b = a+1; } int c = a+1; } //测试函数的局部变量表中要存储的变量包括:this、a、b、c四个,但是因为b出了作用域后c才进行的声明所以c重复利用了b的位置。
3.2 操作数栈
- 主要用于保存计算过程的中间结果,同时作为计算过程中变量临时的存储空间,是用数组实现的(虽然是用数组实现的,但是不能通过索引的方式来进行数据访问,只能入栈出栈来完成数据访问)
- 操作时栈同栈帧一起创建,大小在编译期就已经确定了,保存在方法的Code属性中,为max_stack的值
- 操作数栈同局部变量表一样,64位的类型占两个单位、32位占一个深度
- 如果被调用的方法带有返回值的话,其返回值将会被压入后一个栈帧的操作数栈中
- 概念上两个栈帧是相互独立的,但是虚拟机的实现中做了一些优化,将下面栈帧的部分操作数栈与上面栈帧的部分局部变量表重叠在一起,这样就可以在方法运行时共享一部分数据,减少额外参数复制传递的动作
3.3 动态链接
- 每一个栈帧内部都包含一个指向运行时常量池中该栈帧所属方法的引用,目的是为了支持当前方法的代码能够实现动态连接
- 在Java源文件被编译到字节码文件中时,所有的变量和方法引用都作为符号引用保存在class文件的常量池里。动态链接的作用就是为了将这些符号引用转化为调用方法的直接引用
3.4 方法返回地址
- 存放的是PC寄存器的值
Java方法由两种返回函数的方式(不管哪种方式都会导致栈帧被弹出):
- 正常的函数返回,使用return指令(无返回值类型的方法也是使用return返回的)
- 抛出异常(没有处理异常,即没有try-catch),异常退出时不会给调用者(调用当前方法的方法)任何返回值
3.5 一些附加信息
栈帧中允许携带与JVM实现相关的一些附加信息,但不是必须存在的。
2.2.5 本地方法栈
- 本地方法栈用于管理本地方法的调用。也是线程私有的
- 大小可以固定也可以动态扩展(存在OutOfMemoryError和StackOverflowError)
- 本地方法是使用C实现的
- 执行过程类似于虚拟机栈
在HotSpot JVM中,本地方法栈和虚拟机栈是合二为一的