JVM - 解读GC中的 Safe Point & Safe Region

在这里插入图片描述

Safe Point 安全点

在这里插入图片描述

思考: 如上图 GC的时候,是不是可以马上GC,而不用去care用户线程 ?

答案肯定是不行的。 HotSpot中GC不是在任意位置都可以进入,而只能在safepoint处进入。

JVM在设计的时候在“特定位置”记录了OopMap , 而这些位置被称为安全点。

简单来说

安全点就是指代码运行到这个地方,它的状态是确定的, JVM就可以安全的进行一些操作,比如GC。

所以GC不是想什么时候做就立即触发的,是需要等待所有线程运行到安全点后才能触发。

安全点主要解决的是如何停顿用户线程。

这些特定的安全点位置主要有以下几种:

  • 方法返回之前
  • 调用某个方法之后
  • 抛出异常的位置
  • 循环的末尾
  • …等等

安全点的选定的核心在于: 既不能太少 (太少的话用户线程一直在跑,跑不到SafePoint, 那就没法GC, 并且跑的过程中用户线程也会创建对象,也要占内存,本身需要GC,那就说明内存吃紧了) ,也不能太多 (太多太频繁就意味着运行时内存负荷较高) 。

第二个问题需要考虑: 如何在GC时让用户线程都跑到最近的安全点,然后停下来。 JVM 采取的方式是主动式终端,不直接线程操作,仅简单设置一个标志位,各个程序执行的时候去轮询这个标志,一旦返现中断标志位真就自己在最近的安全点上主动挂起。

轮询标志的地方和安全点是重合的。

既然是轮询,那必须得高效,HotSpot把轮询的操作精简到只有一条汇编指令的程度,使用的是内存保护陷阱的方式。


Safe Region 安全区域

安全似乎解决了如何停顿用户线程,让虚拟机进入GC状态的问题了。 但如果程序“不执行”呢?

举个例子,线程休眠 Thread.sleep(100_000) 休眠100秒,要等100秒才能运行到安全区域啊 ,这可咋玩? 或者用户状态Blocked了 ,这都不执行了,压根就没法跑到safe point点了。。。。。

JVM设计大神引入了 Safe Region 来解决类似问题。

Safe Region 是指在一段代码片段中,引用关系不会发生变化。在这个区域内的任意地方开始 GC 都是安全的。

当用户线程执行到安全区里的代码是,会先标识自己进入了安全区域,那GC的时候就不管这些已经声明自己在安全区域的线程了。


OopMap

GC 我们都知道是清理那些引用不可达的对象, 简单来说 JVM怎样才能够判断出所有位置上的数据是不是指向GC堆里的引用 ?

从外部记录下类型信息,存成映射表 , 这种数据结构被称为 OopMap 。

在HotSpot中,对象的类型信息里有记录自己的OopMap,记录了在该类型的对象内什么偏移量上是什么类型的数据。

oopMap是一个附加的信息,告诉你栈上哪个位置本来是个什么东西。

这个信息是在JIT编译时跟机器码一起产生的。因为只有编译器知道源代码跟产生的代码的对应关系。

每个方法可能会有好几个oopMap,就是根据safepoint把一个方法的代码分成几段,每一段代码一个oopMap,作用域自然也仅限于这一段代码。

循环中引用多个对象,肯定会有多个变量,编译后占据栈上的多个位置。那这段代码的oopMap就会包含多条记录。

每个被JIT编译过后的方法也会在一些特定的位置记录下OopMap,记录了执行到该方法的某条指令的时候,栈上和寄存器里哪些位置是引用。

这样GC在扫描栈的时候就会查询这些OopMap就知道哪里是引用了。这些特定的位置主要在:

  • 1、循环的末尾
  • 2、方法临返回前 / 调用方法的call指令后
  • 3、可能抛异常的位置

这种位置被称为“安全点”(safepoint)。之所以要选择一些特定的位置来记录OopMap,是因为如果对每条指令(的位置)都记录OopMap的话,这些记录就会比较大,那么空间开销会显得不值得。

选用一些比较关键的点来记录就能有效的缩小需要记录的数据量,但仍然能达到区分引用的目的。

因此,HotSpot中GC不是在任意位置都可以进入,而只能在safepoint处进入。

猜你喜欢

转载自blog.csdn.net/yangshangwei/article/details/107119177