⚛️ 运行时数据区

吞佛童子2022年6月9日
  • Java
  • JVM
大约 6 分钟

⚛️ 运行时数据区

线程私有

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 中返回方式
          • 遇到返回的字节码指令正常返回
          • 遇到异常 & 异常没有在方法中做妥善处理,此时异常调用完成
      • 附加信息
        • 允许虚拟机加入一些自定义信息到栈帧中,例如于调试、性能有关的信息,这部分完全取决于具体的虚拟机实现
    • 每个方法被调用直至执行完毕,对应一个栈帧在虚拟机栈中入栈 & 出栈的过程
  • 可能出现的异常
    • OOM :
      • 虚拟机栈的容量允许动态扩展时,当扩展时内存不足,则出现该异常
      • HotSpot 不允许动态扩展
        • 只要申请成功,则不会出现 OOM
        • 但若是申请就失败,则会出现 OOM
    • StackOverflow
      • 线程请求的栈深度超过虚拟机允许的深度

解释器

  1. Javac 编译器完成了程序代码 -> 词法分析 & 语法分析 -> 抽象语法树 -> 生成字节码的过程
  2. 解释器位于 虚拟机内部,完成后续部分
  3. 字节码指令集分类
  • 基于栈的指令集 [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 堆的来回复制
上次编辑于: 2022/10/10 下午8:43:48
贡献者: liuxianzhishou