共计 1934 个字符,预计需要花费 5 分钟才能阅读完成。
以 GC safe-point 引入
GC 如何找到不可用的对象
编写代码的时候是可以知道对象不可用的,但对于程序来说,需要一定的方式来知晓,可用方法比如:编译分析,引用计数,和对象是否可达
可达性分析
一个对象只要能够通过 mutator 触达,那么它就是“活”着的。如果 Mutator 栈的一个槽位包含了对象的引用,那么对象就是直接可触达。而从直接可达对象可触达的对象必定也是可达的,因而可达性分析,只需要找到直接可达的引用。
直接可达的引用就是根引用,根引用的集合就是根的集合
mutator 的上下文就包含了直接可达的数据,所以要获取对象根集合就是要找到 mutator 上下文中的对象引用,而 mutator 的上下文指的就是它的栈、它的寄存器文件以及一些线程上特定的数据
全局数据本身也是直接可达的
可达性分析为了确保能正确的决定对象是否存活,GC 需要获取 mutator 上下文的一致性快照,然后枚举所有的根对象。
这里的一致性指的是 快照的抽取就像只在一个时间点发生,来避免丢失一些活着的对象
如何获取 mutator 上下文的一致性快照
一种简单的方式就是在跟引用的过程中暂停所有的线程。当 mutator 暂停了它的执行时,只有将所有引用信息保存在其上下文中,才能枚举根的集合,这意味着,mutator 需要能够告知那些栈的槽位有一用,那些寄存器持有引用。如果 GC 能够准确的获取上述引用信息,它就称作精准根集合枚举。
无法获取就是不精准的,以下只讲精准的
如何获取精准的引用信息枚举
对于 java 来说,JIT 知晓所有的栈帧信息和寄存器的内容,当 JIT 编译一个方法时,对于每条指令,它都可以去保存根引用信息,保存意味着额外的存储空间,如果要存储所有的指令就显得花销太大,另外在真实的运行过程中也只有少数指令才会成为暂停点,因此 JIT 只需要保存这些指令点的信息就够了。而真正有机会成为暂停点的地方就称作 safe-points,即能够安全的枚举根集合的暂停点。
safe-point 定义
“A point in program where the state of execution is known by the VM”,即代码中 VM 能够准确知道执行状态的位置。safe-point 有多个种类
GC safepoint,要触发一次 GC,JVM 中的所有线程都必须达到 GC safepoint
Deoptimization safepoint, 要触发一次 deoptimization,需要执行 deoptimization 的线程要到达 safepoint 之后才可以开始 deoptimize
Hotspot 中两者实现在一起,概念上没有直接联系,需要数据不一样
如何保证 mutator 会在 safe-point 暂停
当 GC 想要触发一次回收时,它会设置一个标志,mutator 则周期性的去检查 (poll) 这个标志,如果检查到了,就会立马暂停,这里的检查点 (poll points) 也是安全点,由 JIT 负责把 poll points 放到合适的位置
那些地方适合设置检查 GC 事件的标记
polling point 插入的主要原则是:
polling point 应该足够多,防止 GC 等一个 mutator 的暂停太长,导致其他 mutator 都走在等 GC 释放空间,程序整个等待过长
polling point 不能太频繁导致运行时存储开销过大
polling 本身也是有开销的,不能过多
权衡下来只在必须和必要的地方加
在分配地址的时候强制添加,因为分配空间很有肯能导致回收,所以这里是一个安全点
长时间的执行一般意味着循环和方法调用,所以方法调用和循环返回最好加上
但是有时候并不是长时间的执行,而是长时间的空闲,比如 sleep、block,线程在执行其他的 native 函数,这些时候 JVM 无法掌控执行能力,也就无法响应 GC 事件。
不同的 JVM 选用不同的位置放置 safepoint。
如何解决 sleep/block 带来的问题
引用 safe-region。safe-region 是指代码快中没有用到会变异的部分,这样的代码块中,任何一个点都可以安全的枚举根。当进入到 safe-region 中时,mutator 会设置一个准备标记,在离开 safe-region 区域之前,会检查 GC 是否已经完成了回收,如果没有,那么就暂停执行,如果有,就可以直接离开 safe-region 区域,不需要暂停 mutator
文章翻译自 Xiao-Feng Li 博客
rednaxelafx 对 safepoint 的回答
总结
代码的执行过程中,如果需要执行某些操作,比如 GC,deoptimize, 等等,必须知道当前程序所有线程运行到的地方,是否能够恰好满足我执行对应操作,而不会对应用程序本身造成损害,这些能够正确执行操作的地方也就是 safepoint/saferegion