他们说压缩垃圾收集器比传统的内存管理更快,因为它们只需要收集活动对象,并通过在内存中重新排列它们,所以一切都在一个连续的块中,最终没有堆碎片。但是如何快速完成呢?在我看来,这相当于bin-packing问题,它是NP-hard并且在我们关于计算的当前限制内的大数据集上无法在合理的时间内完成。我错过了什么?
答案 0 :(得分:31)
压缩意味着在RAM中移动对象,以便删除某些对象(GC应该回收的死对象),并且所有剩余的对象在RAM中都是连续的。
大多数压缩GC通过在从操作系统获得的单连续区域内分配对象来工作。然后压缩就像移除死对象,然后将所有剩余的活动对象“推向左侧”,挤出孔。如果GC通过压缩工作,则分配只是向上移动“分配区域的结束”指针。综合地,在分配区域内,有一个指针,使得空闲区域由该指针之后的字节组成。要为对象分配空间,指针只需按新对象大小向上移动即可。有时,GC会决定是时候运行,检测死对象,挤出空洞,从而降低分配指针。
压缩GC的性能提升来自多个来源:
如果操作系统拒绝提供单个分配区域,而是产生几个块,那么事情会变得有点复杂,并且可能开始看起来像bin-packing问题,因为压缩然后GC必须决定每个活动对象在哪个块中。然而,装箱的复杂性是关于在一般情况下找到“完美”的匹配;一个近似的解决方案已经足够用于内存分配器。
压缩算法中的算法难点在于更新所有指针,以便它们指向新的对象位置。通过严格的键入,.NET虚拟机可以明确地判断RAM中的每个字是否是指针,但是在不使用太多额外RAM的情况下有效地更新所有指针可能会非常棘手。 H.B.M. Jonkers在“快速垃圾压缩算法”(Information Processing Letters,第9卷,第1期,1979年,第26-30页)中描述了一种非常智能的算法。我在Vast Internet上找不到该论文的副本,但该算法在Jones和Lins的"Garbage Collection"书中有所描述(第5.6节)。我热烈地向任何有兴趣了解垃圾收集者的人推荐这本书。 Jonkers的算法需要在活动对象上进行两次线性传递,结果很容易实现(几十行代码,不再需要;最困难的部分是理解其工作原理)。
额外的复杂性来自世代收藏家,它们大多数时间都试图保持大多数物体不受影响,仅优先使用年轻物体。在这里,这意味着仅压缩堆的末端;完全压实很少应用。这里的要点是,完整的压缩虽然是线性的,但仍然会引起明显的停顿。分代GC试图让这种停顿变得更加罕见。再一次,琼斯和林斯的书是必读的。