如果 JVM 在执行 GC 时不断移动对象,它如何解析引用?

2022-09-01 23:37:38

我正在阅读有关JVM调优的书籍,我突然想到JVM在进行GC时会不断移动对象。但是Java对象具有相互引用,人们会假设这些引用是作为指针实现的,但是JVM不可能在每次移动对象后都遍历整个堆,并更新所有引用;当然,这需要永远。那么,如果引用没有改变,但对象的物理位置却发生了变化,它是如何解析引用的呢?

我读过很多关于JVM的文章,但这从未在任何地方得到解释,甚至没有暗示过。

[编辑]我的观点是,引用是单向的。从指针到指向点是“即时的”,但相反,需要进行完整的堆扫描。虽然这是可能的,但似乎不太可能。如果 10K 对象在次要集合中幸存下来,那么执行 10K 次完整堆扫描 10K 次来更新对这些对象的引用需要多长时间?必须使用某种优化的算法或结构。


答案 1

如果你真的对垃圾收集器的工作原理感兴趣,我可以推荐理查德·琼斯(Richard Jones)关于垃圾收集的2本书吗?链接/参考资料在这里。这并不是专门针对 Java 垃圾回收的。

(我有一本旧书,新书在我的购物清单上。


下面是复制收集器如何处理此问题的简单版本。

复制收集器的工作原理是将对象从一个空间(从空间)复制到另一个空间(到空间)。

具体而言,GC 从每个 GC 根开始,在“从”空间中遍历可访问对象的图形。每次找到对节点的引用(在实例字段、静态字段、堆栈帧等中)时,它都会检查引用指向的对象,以查看它是否被标记为已访问。

  • 如果尚未标记,则 GC 执行以下操作:

    1. 它在空间内标记对象。
    2. 它将对象复制到到空间中。
    3. 它将对象的地址存储在空间空间对象中的空间中。(这就像一个转发地址。
    4. 它以递归方式访问对象的到空间副本的每个引用字段。

    其结果是对空间物体的引用。

  • 如果对象已被标记,则 GC 将查找转发地址,并返回该地址。

然后,GC 从中获取引用的位置(在空间或某些 GC 根中)将使用指向空间中对象的指针进行更新。

如果您遵循所有这些,那么您将看到GC不需要去查找所有包含给定移动对象引用的位置。相反,它只是遇到可访问对象遍历中的所有位置。当然,GC确实必须进行这种遍历,但是有各种技术可以减少每个GC周期中需要完成的遍历量。

如果您没有遵循上述内容,那么请去阅读我推荐的教科书之一。他们会比我做得更好。您还将找到有关其他类型的GC如何处理此问题的材料。


Java HotSpot GC都是复制一种或另一种形式的收集器。对于并行和并发收集,事情比我上面描述的要复杂一些,但是“转发地址”机制是所有通用的。

(关于HotSpot GC的已发表论文或其他公共文档并不多,并且存在的大多数材料都假设读者对现代垃圾收集器的工作原理有很好的了解。


答案 2

JVM 不可能在每次移动对象后都遍历整个堆,并更新所有引用

我自己不是GC方面的专家,但据我所知,这或多或少就是它的作用。例如,请参阅此文本:

相反,复制收集器在遍历可访问对象时将对象复制到另一个内存区域。[...]在进行此类遍历后,所有幸存的对象都驻留在连续的内存区域中,并且所有指针都已更新为指向新的对象位置。[...]在此过程中,GC 会构建一个对象图来跟踪“活动”对象,以便它可以更新对它移动的任何对象的引用。

( http://wiki.osdev.org/Garbage_collection#Copy_collectors,强调我的)。

至于这种“永远”——复制(或移动)垃圾回收器背后的主要思想是,实际上只需要移动少量对象,因为大多数实例已经死了(即大多数实例都非常短暂)。因此,移动的对象数量很少,希望指向它们的引用数量也相当小。

无论如何,GC无论如何都必须构建一个对象引用列表(找出哪些对象仍然被引用/活动并且需要复制),因此它可以重用该列表来更新引用。因此,唯一的更新是“额外的工作”。


推荐