简而言之:垃圾收集器不使用递归。它们仅通过跟踪本质上两组(可以合并)来控制跟踪。跟踪和单元处理的顺序无关紧要,这为表示集合提供了很大的实现自由。因此,有许多解决方案实际上在内存使用上非常便宜。这是必不可少的,因为在堆内存用完时会精确调用GC。大型虚拟内存的情况有所不同,因为可以轻松分配新页面,而且麻烦不是缺少空间,而是缺少数据
局部性。
我假设您正在考虑跟踪垃圾收集器,而不是对您的问题似乎不适用的引用计数。
问题集中在跟踪用于跟踪集合的内存成本:可访问存储单元的集合(对于未跟踪),仍包含尚未跟踪的指针。这仅
是垃圾回收的内存问题的一半。GC还必须跟踪另一个集合:已发现所有可访问单元的集合V(用于访问),以便在过程结束时回收所有其他单元。讨论一个而不是另一个的意义有限,因为它们可能具有相似的成本,使用相似的解决方案,甚至可以合并使用。UV
首先要注意的是,所有跟踪GC都遵循相同的抽象模型,这是基于对程序可访问内存中单元格的有向图的系统研究,其中内存单元为顶点,指针为有向边。为此,它使用以下设置:
仅和U或UVUU和T需要以某种方式表示,该算法才能起作用。
该算法从运行时系统已知的一些根指针开始(通常是堆栈分配的内存中的指针),并将它们指向的所有单元格放入未跟踪的集合U(因此在V也是如此)。
然后收集器将单元格一一取出,并检查每个单元格c的所有指针。对于每个指针,如果指向的单元格位于V中,则不执行任何操作,否则将指向的单元格添加到U,因为尚未检查其指针。处理完所有指针后,单元格c从未跟踪集U转移到跟踪集TUcVUcUT。
当为空时,跟踪终止。这肯定会发生,因为没有任何一个单元会多次通过U。到那时,V = T,并且已知V中的所有单元都是程序可访问的,因此不可回收。V的补H - VUUV=TVH−VV在堆确定哪些细胞是由增变程序不可访问,并且可以通过收集器,用于将来分配给增变被回收。
当然,细节取决于集合的实现方式以及是和U还是U和VUUT有效表示的
我还将跳过有关什么是单元格的详细信息,无论它们是一种还是多种,我们如何在其中找到指针,如何压缩它们,以及许多其他技术问题,您可以在有关垃圾收集的书籍和调查中找到这些问题。 。
您可能已经注意到,这是一个非常简单的算法。没有递归,只有集合U的元素上有一个循环U可以随着处理而增长,直到最终清空为止。没有关于额外内存的先验假设。
允许识别集合并以足够便宜的方式执行所需操作的方法。请注意,处理单元的顺序无关紧要(对下推堆栈没有特殊要求),这为选择有效表示集合的方式提供了很大的自由度。
这些集合的实际表示方式与已知实现方式不同。实际上已经使用了许多技术:
位图:为一个映射保留了一些存储空间,每个存储单元都有一个位,可使用该单元的地址找到该位。当对应的单元格位于映射定义的集合中时,该位打开。如果仅使用位图,则每个单元仅需要2位。
或者,您可以在每个单元格中留出一个特殊的标记位(或2)以对其进行标记。
list:列出列表中的那些单元格。您不需要堆栈或特定的数据结构。在某些系统中,精明的指针反转技术允许使用很少的额外内存来构建列表,精确地位,其中p是每个单元格的指针数量,可通过位堆栈进一步减少。log2pp
您可以测试有关单元格内容及其指针的谓词。
您可以将单元格重新定位在内存的空闲部分中,该部分仅用于属于所表示集合的所有单元格。
VTTU
您实际上可以结合使用这些技术,即使是单个集合。
如前所述,以上所有内容已由一些实现的垃圾收集器使用,有些人可能觉得很奇怪。这完全取决于实施的各种约束。而且它们的内存使用可能相当便宜,这可能得益于可以为此目的自由选择的处理订单策略,因为它们对最终结果无关紧要。
在新区域中传输单元似乎是最奇怪的一种,实际上是非常普遍的:这称为复制收集。它主要用于虚拟内存。
显然,没有递归,并且不必使用mutator算法堆栈。
另一个重要的一点是,许多现代GC是针对大型虚拟内存实现的。然后,由于可以轻松分配新页面,因此无需占用空间来实现和额外的列表或堆栈。但是,在大型的虚拟记忆中,敌人不是缺乏空间,而是缺乏局部性。然后,代表集合的结构及其使用必须适应保留数据结构和GC执行的局部性。问题不是空间而是时间。与内存溢出相比,不充分的实现更有可能显示出不可接受的速度下降。
由于这些技术的足够长的时间,我没有提及由这些技术的各种组合产生的许多特定算法。