图片库网站建设,网站历史快照,用vs做网站原型,1688外贸前几天看周志明的《深入 Java 虚拟机》#xff0c;感觉对 OopMap 和 RememberedSet 的介绍#xff0c;看起来不太容易理解清楚。今天查了一些资料#xff0c;并结合自己的一些猜想#xff0c;把对这两种数据结构的理解写出来。目的只是为了简单易懂#xff0c;而且多有推测…前几天看周志明的《深入 Java 虚拟机》感觉对 OopMap 和 RememberedSet 的介绍看起来不太容易理解清楚。今天查了一些资料并结合自己的一些猜想把对这两种数据结构的理解写出来。目的只是为了简单易懂而且多有推测可能会有一些理解上的偏差请选择性阅读。
总体而言 OopMap 用于枚举 GC Roots RememberedSet 用于可达性分析。
OopMap
OopMap 记录了栈上本地变量到堆上对象的引用关系。其作用是垃圾收集时收集线程会对栈上的内存进行扫描看看哪些位置存储了 Reference 类型。如果发现某个位置确实存的是 Reference 类型就意味着它所引用的对象这一次不能被回收。但问题是栈上的本地变量表里面只有一部分数据是 Reference 类型的它们是我们所需要的那些非 Reference 类型的数据对我们而言毫无用处但我们还是不得不对整个栈全部扫描一遍这是对时间和资源的一种浪费。
一个很自然的想法是能不能用空间换时间在某个时候把栈上代表引用的位置全部记录下来这样到真正 gc 的时候就可以直接读取而不用再一点一点的扫描了。事实上大部分主流的虚拟机也正是这么做的比如 HotSpot 它使用一种叫做 OopMap 的数据结构来记录这类信息。
我们知道一个线程意味着一个栈一个栈由多个栈帧组成一个栈帧对应着一个方法一个方法里面可能有多个安全点。 gc 发生时程序首先运行到最近的一个安全点停下来然后更新自己的 OopMap 记下栈上哪些位置代表着引用。枚举根节点时递归遍历每个栈帧的 OopMap 通过栈中记录的被引用对象的内存地址即可找到这些对象 GC Roots 。
通过上面的解释我们可以很清楚的看到使用 OopMap 可以避免全栈扫描加快枚举根节点的速度。但这并不是它的全部用意。它的另外一个更根本的作用是可以帮助 HotSpot 实现准确式 GC 个人感觉这才是 OopMap 被设计出来的根本原因提高 GC Roots Enumeration 速度更像是一个“意外的惊喜”。关于准确式 GC 的具体内容如什么叫准确式 GC 什么叫保守式 GC 什么叫半保守式 GC 准确式 GC 有哪些实现思路等等在此不一一说明大家可以参考 找出栈上的指针/引用 这篇文章。需要说明的是该文章的作者是 Oracle HotSpot 虚拟机团队的开发人员。
RememberedSet
RememberedSet 用于处理这类问题比如说新生代 gc 它发生得非常频繁。一般来说 gc 过程是这样的首先枚举根节点。根节点有可能在新生代中也有可能在老年代中。这里由于我们只想收集新生代换句话说不想收集老年代所以没有必要对位于老年代的 GC Roots 做全面的可达性分析。但问题是确实可能存在位于老年代的某个 GC Root它引用了新生代的某个对象这个对象你是不能清除的。那怎么办呢
仍然是拿空间换时间的办法。事实上对于位于不同年代对象之间的引用关系虚拟机会在程序运行过程中给记录下来。对应上面所举的例子“老年代对象引用新生代对象”这种关系会在引用关系发生时在新生代边上专门开辟一块空间记录下来这就是 RememberedSet 。所以“新生代的 GC Roots ” “ RememberedSet 存储的内容”才是新生代收集时真正的 GC Roots 。然后就可以以此为据在新生代上做可达性分析进行垃圾回收。
我们知道 G1 收集器使用的是化整为零的思想把一块大的内存划分成很多个域 Region 。但问题是难免有一个 Region 中的对象引用另一个 Region 中对象的情况。为了达到可以以 Region 为单位进行垃圾回收的目的 G1 收集器也使用了 RememberedSet 这种技术在各个 Region 上记录自家的对象被外面对象引用的情况。