2019独角兽企业重金招聘Python工程师标准>>>
安全点
JVM进行垃圾回收是一个非常复杂的过程,如何进行垃圾标记、什么时候进行垃圾、如果进行垃圾回收等等都非常复杂,当前主流测JVM在垃圾回收时都会进行STW(stop the world),即使宣称非常快的CMS垃圾回收期早期也会STW标记垃圾状态。那么这里有个问题,什么时候进行标记对象是否可以被回收呢?
CPU在执行运算过程时需要把数据从内存中载入到寄存器,运算完后再从寄存器中载入到内存中,Java中对象地址也是这么个过程,设想如果一个Java线程分配一个对象,此时对象的地址还在寄存器中,这时候这个线程失去了CPU 时间片,而此时STW GC发现没有任何GC ROOTS与该对象关联起来,此时这个对象呗认为是垃圾并被回收了,之后CPU重新获得时间片后发现此时对象已经不存在了这时候程序就GG了。
因此不是在任何时候都可以随便GC的,复杂的JVM早就考虑到这个问题,在JVM里面引入了一个叫安全点(Safe Point)的东西来避免这个问题。GC的目的是帮助我们回收不再使用的内存,在多线程环境下这种回收将会变得非常复杂,要安全地回收需要满足一下两个条件:
1.堆内存的变化是受控制的,最好所有的线程全部停止。
2.堆中的对象是已知的,不存在不再使用的对象很难找到或者找不到即堆中的对象状态都是可知的。
为了准确安全地回收内存,JVM是在Safe Point点时才进行回收,所谓Safe Point就是Java线程执行到某个位置这时候JVM能够安全、可控的回收对象,这样就不会导致上所说的回收正在使用的对象。
既然达到Safe Point就可以安全准确的GC,name如何到达Safe Point。
说到这里就要提到如何使线程中断,一般有两种方式:主动式和被动式。主动式JVM设置一个全局变量,线程去按照某种策略检查这个变量一旦发现是Safe Point就主动挂起,被动式就是发个信号,例如关机、Control+C,带来的问题就是不可控,发信号的时候不知道线程处于什么状态。这里HostSop虚拟机采用的是主动式使线程中断。
既然JVM使用的是主动性主动到达安全点,那么应该在什么地方设置全局变量呢?显然不能随意设置全局变量,进入安全点有个默认策略那就是:“避免程序长时间运行而不进入Safe Point”,程序要GC了必须要等线程进入安全点,如果线程长时间不进入安全点这样就比较糟糕了,因此安全点主要咋以下位置设置:
1. 循环的末尾
2. 方法返回前
3. 调用方法的call之后
4. 抛出异常的位置
安全区域
安全点完美的解决了如何进入GC问题,实际情况可能比这个更复杂,但是如果程序长时间不执行,比如线程调用的sleep方法,这时候程序无法响应JVM中断请求这时候线程无法到达安全点,显然JVM也不可能等待程序唤醒,这时候就需要安全区域了。
安全区域是指一段代码片中,引用关系不会发生变化,在这个区域任何地方GC都是安全的,安全区域可以看做是安全点的一个扩展。线程执行到安全区域的代码时,首先标识自己进入了安全区域,这样GC时就不用管进入安全区域的线层了,线层要离开安全区域时就检查JVM是否完成了GC Roots枚举,如果完成就继续执行,如果没有完成就等待直到收到可以安全离开的信号。