jvm安全点与安全区域

551 阅读4分钟

这是我参与11月更文挑战的第15天,活动详情查看:2021最后一次更文挑战

寄了又忘,忘了又记,反反复复如此循环,写博客以记之。

凭借印象理解下安全点:

JVM垃圾回收需要从GC Root(static变量,局部变量表的变量)开始遍历出哪些是垃圾对象。但是jvm堆中对象如此之多,一个一个遍历属实慢啊(可以想象需要去每个线程的栈帧获取到局部变量表,然后循环每个局部变量饮用的对象标记为非垃圾,在循环对象的每个属性,找到饮用的对象标记,如此反反复复,这个速度可想而知有多慢)。如果等到GC再去遍历,属实不行啊,太慢了。因为垃圾都是在方法执行时引用赋值产生的,所以能不能在引用赋值时动态的在线程栈中维护一个数据结构来表示哪些对象已经是垃圾了呢,如此就可以减少遍历的GC Root,从而提高寻找垃圾的速度。

这的确是个好办法,但是引用赋值这个操作在代码也是太多了吧,几乎可以说是每行代码都是了,在每行代码在增加一个操作,CPU要执行的指令基本要涨一倍。那么能不能只在某个地方进行监控呢?哎,jvm也想到了,它称这个地方为安全点,safepoint。

但是问题也同样出现了:

  1. 之前执行的引用赋值操作这么办?
  2. safepoint应该选择哪些位置?

好吧我自己都编不下去了(0.0)还是老老实实看下书复习复习。

安全点

还是从GC Root开始,垃圾回收首先需要从GC Root遍历垃圾对象,并且此时需要暂停用户线程,你问不暂停有啥后果(可以想象你一边扫地,旁边一人一边嗑瓜子),也没啥问题,就是更累一些,对应到实现就是更复杂一些,最新的垃圾收集器入g1,cms是支持gc线程用户线程同时运行。但是第一版的实现基本都以功能为主,都是简单实现。

好了,下一步就是开始遍历,从Gc Root开始遍历gc root持有哪些对象的引用了,Gc root有常量,局部变量表的变量,和栈上的对象,以及寄存器中的对象。对象属性怎么找引用? 对于普通对象可以通过反射获取,但是寄存器和栈上的变量怎么获取呢?jvm使用了一种oopmap的数据结构,jvm运行时会将正在执行的指令对于的gc root变量地址放入到oopmap,寄存器中对对象的内存地址也会放入oopmap,普通static类的java对象可以通过Class解析出对象引用的内存地址。

但是为每条指令生成oopmap非常占用CPU以及内存空间,所以jvm只在“具备长时间运行特征的”指令生成oopmap,这个指令的位置也称作安全点。由于只在这个地方存在oopmap,所以当某个线程由于分配内存不够产生gc时,该线程阻塞。gc线程查看其它用户线程是否进入安全点,等待其它线程都进入到最近的安全点,垃圾回收才会正式开始。

为什么选择长时间运行特征的指令呢?

首先是不能任何指令都生成oopmap,cpu和内存开销太大。其次如果指令太短,运行太快,可能gc还没出发指令都跑完了,这时候还需要设置更多的安全点,保证gc运行。但如果安全点设置的太多又会导致空间开销和cpu开销。最好的方法就是设置在指令重复运行的地方,比如方法调用,循环跳转,异常跳转这些地方。

安全区域

等待安全点进行gc是正常的情况,但往往有些线程已经是处于blocked/wait/sleep状态了,没办法进入到安全点。但实际上处于这个状态的线程执行时候并不会导致引用关系的变更,也即不会产生垃圾,所以对于这几个状态的代码区域,jvm称之为安全区域。处于安全区域与处于安全点相同,都可以进行gc。