⚛️ 运行时数据区
2022年6月9日
- Java
⚛️ 运行时数据区
线程私有
1. 程序计数器
- 记录程序运行到的位置,方便线程切换后能够返回继续进行
- 多线程运行时,每个线程都有自己的程序计数器,每个线程任意时刻只能运行一个方法中的代码
- 值:
Java
方法: 虚拟机字节码指令的地址Native
方法:undefined
- 唯一不会
OOM
的区域 - 程序计数器存储的是字节码文件的行号,而这个范围是可知晓的,在一开始分配内存时就可以分配一个绝对不会溢出的内存
2. 虚拟机栈
- 描述 Java 方法执行的线程内存模型
- 每个方法执行时,会同步创建一个
栈帧
- 栈帧大小:
- 一个栈帧需要分配多少内存,并不会受到程序运行期变量数据的影响,仅取决于程序源码 & 具体的栈内存布局形式
- 在编译时已经被确定,且写入方法表的 Code 属性中
- 栈帧存储:
局部变量表
- 存放方法参数 & 方法内部定义的 局部变量
- 在被编译为 Class 文件时,该方法所需分配的局部变量表的最大容量已经确定,存储在
Code
属性中的max_locals
数据项中 - 以变量槽 [
slot
] 形式存储,double
long
占用 2 个 slot,遵循 高位对齐,且每次使用必须同时访问 2 个变量槽, - 其余基本数据类型 占用 1 个 slot
- 采用 索引定位 的形式访问局部变量表,下标从
0
开始 - slot 可以复用,并非有多少局部变量就有多少 slot,而是如果局部变量之间互不影响,那么前面的 slot 可以被后面的复用
操作数栈
- 栈的最大深度在编译期已被写入
Code
属性中的max_stacks
数据项中 - 栈中每个元素可以为 包括
long
&double
在内的任意数据类型,32 位数据类型所占 栈容量 == 1,64位数据类型的栈容量 == 2 - Java 虚拟机的解释执行引擎为 基于栈的执行引擎,另一种为 基于寄存器的执行引擎
- 栈的最大深度在编译期已被写入
动态连接
- 栈帧的当前方法指向方法区中 运行时常量池 的一个引用,判断是运行时常量池的哪个方法,记录的是具体方法的内存地址
- 动态连接能够在程序运行期间,将符号引用转换为具体的方法引用
- 为什么说是动态的?
- 牵涉到 继承 | 多态,只有在运行期间才可确定到底是哪个具体方法
- 将符号变量的访问转换为这些变量运行时所在存储结构的偏移量
方法返回地址
- 一个方法开始执行后,只有 2 中返回方式
- 遇到返回的字节码指令正常返回
- 遇到异常 & 异常没有在方法中做妥善处理,此时异常调用完成
- 一个方法开始执行后,只有 2 中返回方式
附加信息
- 允许虚拟机加入一些自定义信息到栈帧中,例如于调试、性能有关的信息,这部分完全取决于具体的虚拟机实现
- 每个方法被调用直至执行完毕,对应一个栈帧在虚拟机栈中入栈 & 出栈的过程
- 每个方法执行时,会同步创建一个
- 可能出现的异常
OOM
:- 虚拟机栈的容量允许动态扩展时,当扩展时内存不足,则出现该异常
HotSpot
不允许动态扩展- 只要申请成功,则不会出现 OOM
- 但若是申请就失败,则会出现 OOM
StackOverflow
:- 线程请求的栈深度超过虚拟机允许的深度
解释器
- Javac 编译器完成了程序代码 -> 词法分析 & 语法分析 -> 抽象语法树 -> 生成字节码的过程
- 解释器位于 虚拟机内部,完成后续部分
- 字节码指令集分类
- 基于栈的指令集 [Java]
- 可移植,不依赖硬件寄存器的约束
- 代码紧凑,每个字节代表一条指令
- 指令数量多,大量入栈出栈,执行速度稍慢
iconst_1
iconst_1
iadd
istore_0
- 基于寄存器的指令集 [x86]
mov eax, 1
add eax, 1
3. 本地方法栈
为虚拟机运行本地 [
Native
] 方法服务可能出现的异常
OOM
StackOverflow
HotSpot
中虚拟机栈 & 本地方法栈 合二为一本地方法
- Java 调用非 java 代码的接口,该方法并非 Java 实现的
- Java 通过
JNI
来调用本地方法, 而本地方法是以库文件的形式存放的(在 WINDOWS 平台上是DLL
文件形式,在 UNIX 机器上是SO
文件形式) - 当 Java 调用的是本地方法时,虚拟机会保持 Java 栈不变,不会在 Java 栈祯中压入新的祯,虚拟机只是简单地动态连接并直接调用指定的本地方法
线程公有
1. 堆
- 在虚拟机启动时创建
- 作用
- 存放实例对象
几乎
所有的对象实例 & 数组都在堆上创建- 例外:
- 逃逸分析 - 即时编译期的优化处理
- 栈上分配 & 标量替换
- 逃逸分析 - 即时编译期的优化处理
- 是垃圾收集 [GC] 主要区域
- 大小
- 固定大小
- 可扩展 [主流]
- 可能出现的异常
OOM
2. 方法区
- 存储内容:
- 已被加载的类型信息
- 常量
- 类静态变量
- 即时编译期的代码缓存
- 用 永久代实现方法区的缺点:
- 更易造成
OOM
- 更易造成
HotSpot
采取措施:JDK 7
将 字符串常量池 & 类静态变量 移到堆中JDK 8
完全废弃永久代,将永久代剩余内容 [类型信息等] 移到本地内存的元空间中
- 可能出现的异常
OOM
[不论是在堆中还是在本地内存中均会出现]
运行时常量池
- 存在于 已加载的类信息中
- Class 中除了存放类的版本、字段、方法等,还有一部分就是常量池表
- 常量池表中存放编译期生成的各种字面量 & 符号引用
- 这部分内容在类加载后会放到 方法区的运行时常量池中
- 此外,还包括
- 运行时产生的新的常量
- 例如,通过
String.intern()
方法产生的字符串
- 可能出现的异常
OOM
[属于方法区的一部分,自然也会出现该异常]
直接内存
- 非虚拟机运行时数据区域
- 可能出现的异常
OOM
- 举例:
NIO
类中的 基于 通道 & 缓冲区的方式,通过Native
函数直接分配堆外内存- 提高了性能,避免了 Java 堆 & Native 堆的来回复制