Follow me on GitHub

何时回收:安全点 & 安全区域

垃圾回收有 3 个基本问题:

  1. 哪些对象需要回收?
  2. 何时回收?
  3. 如何回收?

HotSpot 在安全点、安全区域执行垃圾回收,对应第 2 个问题。

借助 OopMap,HotSpot 可快速枚举 GC root 引用,这是典型的 以空间换时间,问题来了:

JVM 中可导致 引用关系 变化的指令非常多,若为每条指令都生成对应的 OopMap,会浪费大量额外的存储空间。

因此需要权衡在哪里生成 OopMap,HotSpot 有两处:

  • 安全点(Safe Point)
  • 安全区域(Safe Region)

HotSpot 仅在安全点、安全区域更新 OopMap,也只能在这两处 执行垃圾回收

安全点(运行线程)

A point in program where the state of execution is known by the VM.

在 GC 上下文中,the state of execution 就是指 OopMap 的内容,即栈、寄存器等重要区域什么位置定义了 GC 管理的指针。

安全点选取既不能太少也不能太多,HotSpot 选取的安全点有:

  1. 循环的末尾;
  2. 方法返回前;
  3. 异常跳转处;

选择这些位置是为了 避免程序长时间运行而不进入安全点,比如在循环末尾的安全点,可以避免超大循环一致运行,导致系统一直等待该线程达到安全点,最后导致 GC 迟迟不能执行。

全部用户线程 -> 安全点

虚拟机要执行一次 GC,必须等待 全部用户线程都到达安全点,有两种实现方式:

  • 抢占式中断(几乎没有虚拟机使用)
    • GC 发生时,首先中断所有线程,若发现有线程中断的位置不是安全点,就 恢复线程,让他执行到安全点上再次中断;
  • 主动式中断
    • 不直接中断线程,而是设置一个 标志位,JVM 线程执行时轮询该标志位,若标志位为真,就把自己挂起;
    • 在安全点上轮询标志位;

安全区域(阻塞线程)

线程若 一直运行,早晚能进入安全点,从而执行 GC;若线程阻塞,需要通过安全区域解决。

安全区域定义与安全点几乎相同,只是范围从一个点扩展为 一段代码 而已,在属于安全区域的代码片段中:

  1. 引用关系 不再变化;
  2. OopMap 不再变化;
  3. 可执行 GC;

线程进出安全区域规则为:

  • 进入时,线程标识自己已进入安全区域,虚拟机执行 GC 时,无需等待该线程达到安全点;
  • 离开时,虚拟机检查是否已完成根节点枚举 or GC:
    • 若已完成,正常离开;
    • 若未完成,该线程必须等待,直到枚举根节点 or GC 完成后,虚拟机允许它离开安全区域;

参考: