原始链接:https://www.leahy.club/archives/gcroots

最近在复习JVM,在看《深入理解Java虚拟机》中关于GC Roots的描述不是很明白,就收集了相关资料整理如下:

在实际的垃圾回收器实现中,为了实现高性能还必须考虑一下几点:

  • 枚举根节点:

    首先需要明确GC Roots在哪里?

    对于一个 Java 程序而言,对象都位于堆内存块中,存活的那些对象都被根节点引用着,即根节点 GC Roots 是一些引用类型,自然不在堆里,答案是放在栈里,包括:

    • Local variables 本地变量

    • Static variables 静态变量

    • JNI References JNI引用等

    其次如何高效地找到GC Roots并进行可达性分析?

    为了降低STW的时间,虚拟机应该直接知道哪些地方存放着对象引用,那么对于栈中的Reference类型如何知道其具体的引用信息呢?在Hotspot中,使用一组称为OopMap的数据结构来达到这个目的。

    OopMap本质就是映射表,每个对象自身都维护一个OopMap,记录了在该类型的对象内什么偏移量上是什么类型的数据,这些信息是在类加载阶段计算得到的。

    在源代码里面每个变量都是有类型的,但是编译之后的代码就只有变量在栈上的位置了,而这个位置上只存放了地址信息,无法得知其具体的引用信息。那么OopMap是对象自身的一个附加的信息,告诉你栈上哪个位置本来是个什么东西,那么就可以解决这个问题了。 这个信息是在JIT编译时跟机器码一起产生的。因为只有编译器知道源代码跟产生的代码的对应关系。

    所以在栈中扫描GC Roots时,都是在外部维护一个集合GC Roots Set,这个集合中存储Reference类型变量的OopMap信息。再根据具体的OopMap信息来做进一步的可达性分析。

  • 安全点

    有了OopMap,Hotspot就可以快速准确地完成GC Roots枚举。但是在实际使用中,我们只会选择特定的位置来记录OopMap,因为如果对于每条指令位置都记录OopMap的话,这些记录就会造成巨大的空间开销,所以选用一些关键点来记录可以缩小需要记录的数据量。因此,Hotspot中的GC不是任意位置都可以进行的,只能在safepoint才可以进行。

  • 安全区域

    在多线程情况下,光有安全点是不够的,因为无法保证在运行的线程在GC时都处在安全点。安全区域是指在一段代码片段之中,引用关系不会发生变化。在这个区域中的任意地方开始GC都是安全的。我们也可以把Safe Region看做是被扩展了的Safepoint。在线程执行到Safe Region中的代码时,首先标识自己已经进入了Safe Region,那样,当在这段时间里JVM要发起GC时,就不用管标识自己为Safe Region状态的线程了。在线程要离开Safe Region时,它要检查系统是否已经完成了根节点枚举(或者是整个GC过程),如果完成了,那线程就继续执行,否则它就必须等待直到收到可以安全离开Safe Region的信号为止。


参考链接:
[1]https://www.cnblogs.com/strinkbug/p/6376525.html

[2]https://juejin.im/post/5d034d29f265da1bb13f2e5a

[3]https://medium.com/platform-engineer/understanding-java-garbage-collection-54fc9230659a