JVM运行时数据区可分为线程共享的堆,方法区和线程独享的虚拟机栈、本地方法栈、程序计时器此外还有一个单独的直接内存,如下图所述:
程序计数器
程序计数器(Program Counter Register)也叫PC寄存器,每个线程会通过程序计数器记录当前要执行的的字节码指令的地址。
- 起始时计数器记录0号位置指令的地址
- 当0号位置指令执行完成后,计数器记录1号位置指令的地址
- 当1号指令执行完成后,计数器记录2号位置指令的地址
- 依此类推,程序计数器主要功能是记录当前线程将要执行的字节码指令的地址
ps:因为每个线程只存储一个固定长度的内存地址,程序计数器是不会发生内存溢出的。
Java虚拟机栈
Java虚拟机栈(Java Virtual Machine Stack)采用栈的数据结构来管理方法调用中的基本数据,先进后出(First In Last Out),每一个方法的调用使用一个栈帧(Stack Frame)来保存。
如下执行main时相关的java虚拟机栈如下:
Java虚拟机栈随着线程的创建而创建,而回收则会在线程的销毁时进行。由于方法可能会在不同线。程中执行,每个线程都会包含一个自己的虚拟机栈。
栈帧的组成
局部变量表
- NR:编号
- 起始PC:变量生效的起始位置
- 长度:变量生效的有效范围,左闭右开 [ )。
- 序号:变量所在的槽位置
槽覆盖特性,即当某一局部变量不再生效时,当前槽可以重写被再次使用。
操作数栈
- 操作数栈是栈帧中虚拟机在执行指令过程中用来存放中间数据的一块区域。他是一种栈式的数据结构,如果一条指令将一个值压入操作数栈,则后面的指令可以弹出并使用该值。
- 在编译期就可以确定操作数栈的最大深度,从而在执行时正确的分配内存大小。
帧数据
动态链接
当前类的字节码指令引用了其他类的属性或者方法时,需要将符号引用(编号)转换成对应的运行时常量池中的内存地址。动态链接就保存了编号到运行时常量池的内存地址的映射关系。
方法出口
方法出口指的是方法在正确或者异常结束时,当前栈帧会被弹出,同时程序计数器应该指向上一个栈帧中的下一条指令的地址。所以在当前栈帧中,需要存储此方法出口的地址。
异常表
主要作用是存放代码正常执行和出现异常时字节码指令如何进行跳转的数据
栈溢出
- Java虚拟机栈如果栈帧过多,占用内存超过栈内存可以分配的最大大小就会出现内存溢出。
- Java虚拟机栈内存溢出时会出现StackOverflowError的错误
- 如果我们不指定栈的大小,JVM 将创建一个具有默认大小的栈。大小取决于操作系统和计算机的体系结构。
本地方法栈
- Java虚拟机栈存储了Java方法调用时的栈帧,而本地方法栈存储的是native本地方法的栈帧。
- 在Hotspot虚拟机中,Java虚拟机栈和本地方法栈实现上使用了同一个栈空间。本地方法栈会在栈内存上生成一个栈帧,临时保存方法的参数同时方便出现异常时也把本地方法的栈信息打印出来。