垃圾回收器为什么必须要停顿下?
在垃圾收集器在获取根节点这一步时必须暂停用户线程的也就是我们常说的STW,目前可达性分析算法耗时最长的查找引用链的过程已经可以做到和用户线程一起并发,但根节点枚举的获取还必须是要在一个能保证一致性的快照中才能进行。
这里说的一致性就是根节点枚举分析期间执行子系统看起来就像被冻结在某个时间点上,不会出现一边分析,根节点的对象引用关系还在不断的变化的情况。这也是导致垃圾收集过程必须停顿所有用户线程的其中一个重要原因,即便是号称停顿时间可控的CMS、G1、ZGC等,跟节点分析时也是必须要停顿的。
GC Roots
都知道JVM内对象存活判定一般用可达性分析算法,也就是说在HotSpot 使用里面维护了很多根节点(GC Roots)。
GC Roots种类:
静态变量引用的对象
常量引用的对象
栈针本地变量表引用的对象
JNDI 引用的对象
什么是OopMap ?
目前主流JVM垃圾收集,在当用户线程停顿下后其实是不需要一个不漏的检查完所有的执行上下文和全局引用位置的。在HotSpot中是使用一组成为OopMap的数据结构来达到这个目的的。
当类加载动作完成时,HotSpot就会将对象内的类型、偏移量等数据计算出来,这时在垃圾收集器扫描的时候就可以直接得到这些信息了,并不需要一个不漏的从GC Roots开始查找。
借助于OopMap,虚拟机可以快速枚举GC Root引用,这就是典型的以空间换时间。但导致引用关系变化非常多,又不能生成过多的OopMap从而导致存储资源的浪费,所以又出现了安全点和安全区域。
什么是安全点?
在OopMap的帮助下,可以快速的完成GC Roots数据扫描,但可以导致引用关系变化的可能太多了,也就是说导致OopMap内容变化的指令非常多,不可能每次变化都生成对应的OopMap。
所以,在某个特定的位置来记录关系信息到OopMap,这些位置就被称为安全点。其实也就是在代码执行到达指定的位置才能够暂停进行信息收集。
举例Serial 收集器(其他收集器也差不多):
- 单线程收集器,收集的时候会暂停所有用户线程(简称STW)
- 客户端模式下默认收集器
- 简单高效,是所有收集器中额外内存消耗最小的
安全点的选定:具有让程序长时间执行的特征,例如方法调用、循环跳转、异常跳转等。
怎么到达安全点?
在实际情况下,是不可能在发生垃圾收集的时候所有的线程都正好在安全点,所以就需要线程都跑到最近的安全点然后停顿下来。
有两种方案:
抢先试中断(Preemptive Suspension):(现在几乎没有用这种的了)
不需要线程的执行代码主动配合,在垃圾收集发生时,系统首先把所有用户线程全部中断,如果发现有用户线程中断的地方不再安全点上,就恢复这条线程执行,让它一会再重新中断,直到到达安全点。
主动式中断(Voluntary Suspension):
在垃圾收集需要中断线程的时候,不直接对线程操作,仅简单的设置一个标志位,各个线程执行过程时会不停的主动去轮询这个标志,一旦发现中断标志为true时就在自己最近的安全点上主动挂起。
什么是安全区域?
安全点似乎解决了让虚拟机内部线程主动停顿,整个虚拟机进入垃圾回收状态的问题。但在实际情况下,如果线程处于sleep 或Blocked状态的话是没有分配CPU时间的,这时线程是无法响应虚拟机的中断请求,不能再走到安全点进行挂起,而虚拟机也不能持续的等待线程被重新激活分配CPU。这种情况,就必须引入安全区域(Safe Region)来解决问题了。
安全区域,可以看作是安全点的扩展。指的是能够确保在某一段代码片段中,引用关系不会发生变化,在这个区域中任意地方开始来及手机都是安全的。
安全区域内发生了什么?
当线程执行到安全区域里的代码时,会先标识自己进入了安全区域,如果这时段里进行了垃圾收集虚拟机就不必去管这些已经标识过的线程了
当线程离开安全区域时,它要检查下虚拟机是否完成了根节点的扫描或者垃圾收集过程中需要停顿的阶段。如果完成了,那线程就会继续执行。否则就必须一直等待,直到收到可以离开安全区域的信号。
结尾
看完这些,你能回答下面的问题吗!
垃圾收集器为什么必须要停顿下?
安全点和安全区域的区别?
往期推荐