虚拟机是采用主动式中断,使所有线程跑到一个被叫做安全点的地方 停顿下来开始GC,保证了GC过程可达性分析的准确性;同时使用 安全区域 这种方式,跳过那些没有正在执行的线程,避免GC等待。
为什么 GC进行时必须停顿所有Java执行线程(Stop The World)?
GC进行,意味着要回收一些不可用对象,而回收对象需要进行可达性分析(第一次标记),分析时,需要从 GC Roots 节点找引用链,就需要枚举根结点和逐个检查里面的引用,那么必然会消耗很多时间。如果在可达性分析过程中对象引用关系还在不断变化,那么分析结果的准确性就无法保证。
但是,如今主流的Java虚拟机使用的都是准确式 GC ,所以当执行系统停顿下来后,并不需要一个不漏的检查引用,虚拟机应该有办法直接知道哪些地方存放着对象引用。HotSpot的实现中,是使用一组OopMap的数据结构来达到这个目的。在类加载完成后就计算出来了引用位置,并记录在特定位置。
这样GC时就知道引用位置,就不用花时间专门去逐个检查引用。但枚举根结点也是必须要停顿的。
但是,程序执行时并非在所有地方都能停顿下来开始GC,而是只有在到达安全点(Safepoint)时才暂停。
安全点:就是前面用来记录引用信息的特定位置。
安全点的选定:基本上是以程序 “是否具有让程序长时间执行的特征” 为标准进行选定的。因为每条指令执行的时间都非常短暂,程序不太可能因为指令流长度太长这个原因而过长时间运行。而“长时间执行”的最明显特征就是指令序列复用,例如方法调用、循环跳转、异常跳转等,所以具有这些功能的指令才会产生Safepoint。
那如何在GC发生时让所有线程(不包括执行JNI调用的线程)都“跑”到最近的安全点上再停顿下来呢?这里有两种方案,分别是:
- 抢先式中断(Preemptive Suspension):不需要线程的执行代码主动去配合,在GC发生时,首先把所有线程全部中断,如果发现有线程中断的地方不在安全点上,就恢复线程,让它“跑”到安全点上。
- 主动式中断(Voluntary Suspension):当GC需要中断线程的时候,不直接对线程操作,仅仅简单地设置一个标志,各个线程执行时主动去轮询这个标志,发现中断标志为真时,就自己中断挂起。轮询标志的地方和安全点是重合的,另外再加上创建对象需要分配内存的地方。
Safepoint机制保证了程序执行时,在不太长时间内就会遇到可进入GC的Safepoint。但是程序“不执行”的时候呢?程序不执行是指没有分配CPU时间(Sleep状态或Blocked状态),这时候线程无法响应JVM的中断请求。对于这种情况,就需要安全区域(Safe Region)来解决。
安全区域:是指在一段代码片段之中,引用关系不会发生变化。在这个区域中的任何地方开始GC都是安全的。
在线程执行到 Safe Region 中的代码时,首先标识自己已经进入了 Safe Region ,那样,当这段时间里JVM要发起GC时,就不用管标识自己为 Safe Region 状态的线程了。在线程要离开 Safe Region 时,它要检查系统是否已经完成了整个GC过程,如果完成了,那线程就继续执行,否则,它就必须等待直到收到可以安全离开 Safe Region 的信号为止。
如有错误,欢迎留言指正 * _ *