JVM 之 OopMap 和 RememberedSet

    首先非常感谢原文作者的分享,在我学到GC很困惑的时候,找到了这么一篇通俗易懂的帖子。

    原文地址:http://dsxwjhf.iteye.com/blog/2201685

    

OopMap 用于枚举GCRoots

        当垃圾回收时,收集线程会对上的内存进行扫描,看看那些位置上存储了Reference类型。如果发现了某个位置上存储的是Reference类型,就意味着这个引用所指向的对象在这一次垃圾回收过程中不能够回收。

        栈上的本地变量表里面只有一部分数据是Reference类型的,为了避免每次都扫描整个栈,所以采用空间换时间的策略。在某个时候(安全点)把栈上代表引用的位置记录下来,GC时直接读取,避免了全部扫描。 HotSpot虚拟机采用了一种叫做OopMap的数据结构来记录这些引用(OopMap也帮助HotSpot实现了准确式GC),OopMap记录了栈上本地变量到堆上对象的引用关系,这些引用指向的对象不能够回收,并且可以作为根节点来进行可达性分析,查找出不能够回收的对象。

      JVM 之 OopMap 和 RememberedSet_第1张图片

       深入理解Java虚拟机里有段代码清楚的介绍了在某个安全点向OopMap中记录了引用关系的指令:

JVM 之 OopMap 和 RememberedSet_第2张图片

       可以看到在0x026eb7a9处的call指令有OopMap记录,它指明了EBX寄存器和栈中偏移量为16的内存区域中各有一个普通对象指针(Ordinary Object Pointer)的引用,有效范围为从call指令开始直到0x026eb730(指令流的起始位置) +142(OopMap记录的偏移量) = 0x026eb7be, 即hlt指令为止。

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


RememberedSet

        新生代 GC(发生得非常频繁)。一般来说, GC过程是这样的:首先枚举根节点。根节点有可能在新生代中,也有可能在老年代中。这里由于我们只想收集新生代(换句话说,不想收集老年代),所以没有必要对位于老年代的 GC Roots 做全面的可达性分析。但问题是,确实可能存在位于老年代的某个 GC Root,它引用了新生代的某个对象,这个对象你是不能清除的。那怎么办呢? 

JVM 之 OopMap 和 RememberedSet_第3张图片

       事实上,对于位于不同年代对象之间的引用关系,虚拟机会在程序运行过程中给记录下来。对应上面所举的例子,“老年代对象引用新生代对象”这种关系,会在引用关系发生时,在新生代边上专门开辟一块空间记录下来,这就是RememberedSet,RememberedSet记录的是新生代的对象被老年代引用的关系。所以“新生代的 GC Roots ” + “ RememberedSet 存储的内容”,才是新生代收集时真正的 GC Roots 。然后就可以以此为据,在新生代上做可达性分析,进行垃圾回收。

   

       G1 收集器使用的是化整为零的思想,把一块大的内存划分成很多个域( Region )。但问题是,难免有一个 Region 中的对象引用另一个 Region 中对象的情况。为了达到可以以 Region 为单位进行垃圾回收的目的, G1 收集器也使用了 RememberedSet 这种技术。G1中每个Region都有一个与之对应的RememberedSet ,在各个 Region 上记录自家的对象被外面对象引用的情况。当进行内存回收时,在GC根节点的枚举范围中加入RememberedSet 即可保证不对全堆扫描也不会有遗漏。


你可能感兴趣的:(java,jvm,jvm)