如何实现垃圾收集器?

时间:2011-07-28 22:28:40

标签: garbage-collection

有人能指出我如何实现垃圾收集的良好来源吗?我正在制作类似lisp的解释语言。它目前使用引用计数,但当然不能释放循环依赖的对象。

我一直在阅读标记和扫描,三色标记,移动和不移动,增量和停止世界,但是......我不知道什么是将对象整齐地分成几组的最佳方法将每个对象的内存开销保持在最低限度,或者如何以递增方式执行操作。

我已经阅读了一些语言,参考计数使用循环参考检测,我可以使用。我知道我可以使用像Boehm这样的免费收藏家,但我想学习如何自己动手。

对于没有像我这样的话题没有经验的人,我会感谢任何在线资料和某种教程或帮助。

8 个答案:

答案 0 :(得分:29)

  

有人能指出我如何实现垃圾收集的良好来源吗?

那里有很多关于垃圾收集的高级资料。 Garbage Collection Handbook很棒。但我发现有一些宝贵的基本介绍信息,所以我写了一些关于它的文章。 Prototyping a mark-sweep garbage collector描述了用F#编写的最小标记扫描GC。 Very Concurrent Garbage Collector描述了一个更高级的并发收集器。 HLVM是我编写的一个虚拟机,包括一个处理线程的stop-the-world收集器。

实现垃圾收集器的最简单方法是:

  1. 确保您可以整理全局根源。这些是包含对堆的引用的局部变量和全局变量。对于局部变量,在其范围的持续时间内将它们推送到影子堆栈。

  2. 确保您可以遍历堆,例如堆中的每个值都是一个实现Visit方法的对象,该方法返回该对象的所有引用。

  3. 保留所有已分配值的集合。

  4. 通过调用malloc并将指针插入所有已分配值的集合来分配。

  5. 当所有分配值的总大小超过配额时,启动标记然后扫描阶段。这会以递归方式遍历堆积所有可到达值的集合。

  6. 分配值减去可达值的设置差异是不可达值的集合。迭代它们调用free并从分配值集中删除它们。

  7. 将配额设置为所有已分配值总大小的两倍。

答案 1 :(得分:8)

查看以下页面。它有很多链接。 http://lua-users.org/wiki/GarbageCollection

答案 2 :(得分:7)

正如delnan所建议的那样,我从一个非常朴素的世界三色标记和扫描算法开始。我设法通过使对象列表节点将对象保留在集合中,但它确实为每个对象添加了大量数据(虚拟指针,两个指向节点的指针,一个用于保存颜色的枚举)。它工作得很好,没有内存丢失在valgrind上:)从这里我可能会尝试添加一个免费列表进行回收,或某种检测何时可以方便地停止世界,或增量方法或特殊分配器避免碎片或其他东西。如果你能指出我在哪里可以找到关于如何做这些事情或做什么的信息或建议(我不知道你是否可以评论一个已回答的问题),我会非常感激。我将在此期间检查Lua的GC。

答案 3 :(得分:4)

我在大约400 SLOC中用C实现了Cheney-style copying garbage collector。我为一种静态类型的语言做了这件事,令我惊讶的是,更难的部分实际上是在传达信息,事物是指针,哪些事情不是。在动态类型语言中,这可能更容易,因为您必须已经使用某种形式的标记方案。

还有一个关于垃圾收集的标准书的新版本:"The Garbage Collection Handbook: The Art of Automatic Memory Management" by Jones, Hosking, Moss。 (亚马逊英国网站称2011年8月19日。)

答案 4 :(得分:4)

我还没有提到的一件事是使用内存句柄。如果每个对象引用是指向包含所讨论对象的真实地址的结构的指针,则可以避免对内存加倍的需要(如切尼式复制算法所需)。对内存对象使用句柄会使某些例程变得更慢(必须重新读取对象的内存地址,任何时候可能发生的事情会移动它)但对于单线程系统,垃圾收集只会在可预测的时间发生,这个并不是一个问题,也不需要特殊的编译器支持(多线程GC系统可能需要编译器生成的元数据,无论它们是使用句柄还是直接指针)。

如果一个人使用句柄,并使用一个链接列表进行实时句柄(相同的存储可用于保存需要重新分配的死句的链表),可以在标记每个句柄的主记录之后继续执行处理列表,按分配顺序,并将该句柄引用的块复制到堆的开头。因为句柄将按顺序复制,所以不需要使用第二个堆区域。此外,可以通过跟踪一些堆顶指针来支持代。在压缩内存时,首先只是对自上次GC后添加的项目进行压缩。如果这没有释放足够的空间,则自上一级1 GC以来添加的项目变得紧凑。如果这没有释放足够的空间,那么就会使一切变得紧凑。标记阶段可能必须对所有世代的物体起作用,但昂贵的紧凑阶段不会。

实际上,使用基于句柄的方法,如果一个人正在标记所有代的东西,那么如果需要,每个GC上的计算可以传递每代中可以释放的空间量。如果Gen2中的一半对象已经死亡,那么进行Gen2集合可能是值得的,以便减少Gen1集合的频率。

答案 5 :(得分:3)

Lisp中的垃圾收集实现

建立LISP | http://www.lwh.jp/lisp/

阿卡迪亚| https://github.com/kimtg/arcadia

答案 6 :(得分:2)

阅读Memory Management: Algorithms and Implementations in C/C++。这是一个很好的起点。

答案 7 :(得分:0)

我正在为我的postscript解释器做类似的工作。 more info via my question.我同意Delnan的观点,即简单的标记扫描算法是一个很好的起点。您需要为所有容器设置标记,复选标记,清除标记和迭代器的功能。一个简单的优化是在分配新对象时清除标记,并在扫描期间清除标记;否则在开始设置之前你需要一整个通行证来清除标记。