jvm之safepoint、safeRegion和OopMap

前情说明

本博客主要是对文末列出的参考博客进行一个汇总整理,尽管也加上了一些个人的理解,但也不能算原创,但无奈csdn没有“整理”这种类型,因而还是挂成了原创。将这些零散的博客整理到一起有两方面的考虑:一方面是方便自己以后回顾,另一方面也方便大家进行系统性地学习。

正文

safepoint

JVM在很多场景下使用到safepoint,最常见的场景就是GC的时候。对一个Java线程来说,它要么处在safepoint,要么不在safepoint。下面列出了需要线程处于safepoint下才能执行的操作:

  1. Garbage collection pauses(垃圾回收的标记阶段
  2. Code deoptimization(代码反优化,目前还不知道是什么)
  3. Flushing code cache(目前不知道是什么)
  4. Class redefinition (e.g. hot swap or instrumentation,class类动态修改)
  5. Biased lock revocation(synchronized关键字对应的偏向锁撤销
  6. Various debug operation (e.g. deadlock check or stacktrace dump,执行debug或者执行jstack命令等)

safepoint可以理解成是在代码执行过程中的一些特殊位置,当线程执行到这些位置的时候,说明虚拟机当前的状态是安全的,如果有需要,可以在这个位置暂停,比如发生GC时,需要暂停暂停所以活动线程,但是线程在这个时刻,还没有执行到一个安全点,所以该线程应该继续执行,到达下一个安全点的时候暂停,等待GC结束。JVM维护了一个数据结构,记录了所有的线程,所以它可以快速检查所有线程的状态。当有GC请求时,所有进入到safepoint的Java线程会在一个Thread_Lock锁阻塞,直到当JVM操作完成后,VM释放Thread_Lock,阻塞的Java线程才能继续运行。

safepoint指的特定位置主要有:

  1. 循环的末尾 (防止大循环的时候一直不进入safepoint,而其他线程在等待它进入safepoint)
  2. 方法返回前
  3. 调用方法的call之后
  4. 抛出异常的位置

为什么需要safepoint

为了让用户程序能正确地执行,JVM在背后需要维护各种数据信息,比如本文即将介绍的用于协助完成GC操作的OopMap,这些数据信息在修改前需要将其它所有线程在safepoint挂起,等到修改完成之后,再将所有线程恢复执行,以避免因线程并发而导致维护的数据不正确;此外,一些JVM操作,比如线程debug,以及线程栈导出等,这些操作也都需要将其它所有的线程在safepoint挂起,只剩下对应的线程来执行对应的操作。

safepoint与GC

GC的标记阶段需要stop the world,让所有Java线程挂起,这样JVM才可以安全地来标记对象。safepoint可以用来实现让所有Java线程挂起的需求。也就是说,执行GC的VMThread需要等待所有线程进入到safepoint之后才能进行。这意味着,如果有线程长时间运行而没有进入safepoint,那么GC也无法开始,JVM可能进入到“假死”状态,因为其它正常的线程都在阻塞在safepoint并等待被唤醒。

我们知道,JVM有两种执行方式:解释型编译型(JIT),JVM要保证这两种执行方式下safepoint都能工作。

在JIT执行方式下,JIT编译的时候直接把safepoint的检查代码加入了生成的本地代码,当JVM需要让Java线程进入safepoint的时候,只需要设置一个标志位,让Java线程运行到safepoint的时候主动检查这个标志位,如果标志被设置,那么线程停顿,如果没有被设置,那么继续执行。

在解释器执行方式下,JVM会设置一个2字节的dispatch tables,解释器执行的时候会经常去检查这个dispatch tables,当有safepoint请求的时候,就会让线程去进行safepoint检查。

虽然前面提到在循环的末尾会插入safepoint,但参考博客8中提到JVM认为比较短的循环,比如以int作为index的循环,为了提高性能,是不会插入safepoint的,但我们知道,int的最大值是2147483647,因而正如该博客所说,当以int作为index的循环的上限值很大的时候,会导致虚拟机等待进入GC的耗时很长;而对于以long作为index的循环,则会在每次循环回跳之前插入safepoint,从而避免等待耗时长的问题。

参考博客8中还提到使用-XX:+UseCountedLoopSafepoints参数可以强制在Counted loop循环回跳之前插入Safepoint,也就是说即使循环比较短,JVM也会帮忙插入Safepoint了,用于防止大循环执行时间过长导致进入Safepoint卡住的问题。但是这个参数在JDK8上是有Bug的,可能会导致JVM Crash,而且是到JDK9才修复的。

safeRegion

safepoint只能处理正在运行的线程,它们可以主动运行到safepoint。而一些Sleep或者被blocked的线程不能主动运行到safepoint。这些线程也需要在GC的时候被标记检查,JVM引入了safe region的概念。safe region是指一块区域,这块区域中的引用都不会被修改,比如线程被阻塞了,那么它的线程堆栈中的引用是不会被修改的,JVM可以安全地进行标记。线程进入到safe region的时候先标识自己进入了safe region,等它被唤醒准备离开safe region的时候,先检查能否离开,如果GC已经完成,那么可以离开,否则就在safe region呆着。这可以理解,因为如果GC还没完成,那么这些在safe region中的线程也是被stop the world所影响的线程的一部分,如果让他们可以正常执行了,可能会影响标记的结果。

OopMap

由参考博客1可知,gc roots包括了虚拟机线程栈(栈桢中的本地变量表)中的引用的对象,这意味着gc线程在垃圾收集时,需要对栈上的内存进行扫描,看看哪些位置存储了Reference 类型。如果发现某个位置确实存的是 Reference 类型,就意味着它所引用的对象这一次不能被回收。但问题是,栈上的本地变量表里面只有一部分数据是 Reference 类型的(它们是我们所需要的),那些非 Reference 类型的数据对我们而言毫无用处,但我们还是不得不对整个栈全部扫描一遍,这是对时间和资源的一种浪费。

一个很自然的想法是,能不能用空间换时间,在某个时候把栈上代表引用的位置全部记录下来,这样到真正 gc 的时候就可以直接读取,而不用再一点一点的扫描了。事实上,大部分主流的虚拟机也正是这么做的,比如 HotSpot ,它使用一种叫做 OopMap 的数据结构来记录这类信息。OopMap是ordinary object pointer map的简称,记录了栈上本地变量到堆上对象的引用关系。

我们知道,一个线程意味着一个栈,一个栈由多个栈帧组成,一个栈帧对应着一个方法,一个方法里面可能有多个安全点。 gc 发生时,程序首先运行到最近的一个安全点停下来,然后更新自己的 OopMap ,记下栈上哪些位置代表着引用。枚举根节点时,递归遍历每个栈帧的 OopMap ,通过栈中记录的被引用对象的内存地址,即可找到这些对象( GC Roots )。

参考博客:

1、https://blog.csdn.net/Saintyyu/article/details/102882186 CMS垃圾回收器细节思考与补充

2、https://www.jianshu.com/p/c79c5e02ebe6  JVM源码分析之安全点safepoint

3、https://blog.csdn.net/iter_zc/article/details/41843595 聊聊JVM(五)从JVM角度理解线程

4、https://blog.csdn.net/iter_zc/article/details/41847887 聊聊JVM(六)理解JVM的safepoint

5、https://blog.csdn.net/iter_zc/article/details/41892567 聊聊JVM(九)理解进入safepoint时如何让Java线程全部阻塞

6、https://blog.csdn.net/youyou1543724847/article/details/52728154  oopmap

7、https://stackoverflow.com/questions/20134769/how-to-get-java-stacks-when-jvm-cant-reach-a-safepoint

8、http://blog.itpub.net/31559359/viewspace-2650608/  HBase实战:记一次Safepoint导致长时间STW的踩坑之旅

9、https://www.iteye.com/blog/dsxwjhf-2201685   JVM 之 OopMap 和 RememberedSet

10、https://ask.csdn.net/questions/652787 为什么发生GC的时候要让所有的线程都运行到安全点(Safepoint)处呢

11、https://www.jianshu.com/p/e74fe532e35e  jvm知识整理

12、https://www.cnblogs.com/vsop/p/10383944.html  safepoint与UseCountedLoopSafepoints jdk 1.8.131之前会导致jvm crash

13、https://www.cnblogs.com/twoheads/p/10150063.html  JVM锁简介:偏向锁、轻量级锁和重量级锁

14、https://blog.csdn.net/xw13106209/article/details/6989415  java native方法及JNI实例

15、https://blog.csdn.net/superfjj/article/details/107197580  小师妹学JVM之:JVM中的Safepoints :这些停下来的线程不包括运行native code的线程。因为这些native code是不属于JVM管理的。

16、https://blog.csdn.net/superfjj/article/details/107855767  JVM系列之:再谈java中的safepoint

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值