29

我想了解有关 Erlang/OTP 中垃圾收集 (GC) 和内存管理的技术细节。

但是,我在erlang.org及其文档上找不到。

我在网上找到了一些文章,它们以非常笼统的方式谈论GC,例如使用什么垃圾收集算法。

4

4 回答 4

25

为了对事物进行分类,让我们定义内存布局,然后讨论 GC 是如何工作的。

内存布局

在 Erlang 中,每个执行线程称为一个进程。每个进程都有自己的内存,内存布局由三部分组成:进程控制块堆栈

在此处输入图像描述

  • PCB:进程控制块保存诸如进程标识符 (PID)、当前状态(运行、等待)、其注册名称和其他此类信息之类的信息。

  • 堆栈:它是一个向下增长的内存区域,用于保存传入和传出参数、返回地址、局部变量和用于评估表达式的临时空间。

  • 堆:它是一个向上增长的内存区域,用于保存进程邮箱消息和复合术语。大于 64 字节的二进制项存储在进程专用堆中。它们存储在一个大型共享堆中,所有进程都可以访问。


垃圾收集

目前 Erlang 使用在每个 Erlang 进程私有堆内独立运行的分垃圾收集,并且对于全局共享堆也发生引用计数垃圾收集。

  • Private Heap GC:它是分代的,因此将堆分为两部分:年轻代和老年代。还有两种收集策略;世代(次要)和全扫描(主要)。分代 GC 只收集年轻堆,但全扫描收集年轻堆和老堆。

  • Shared Heap GC:是引用计数。共享堆(Refc)中的每个对象都有一个由其他对象(ProcBin)持有的对它的引用计数器,这些对象存储在 Erlang 进程的私有堆中。如果对象的引用计数器达到零,则该对象已变得不可访问并将被销毁。


要获取更多详细信息和性能提示,请查看我的文章,它是答案的来源:Erlang Garbage Collection Details and Why It Matters

于 2016-01-09T11:14:18.703 回答
20

该算法的参考论文:One Pass Real-Time Generational Mark-Sweep Garbage Collection (1995),Joe Armstrong 和 Robert Virding 在 1995 年(在 CiteSeerX)

抽象的:

传统的标记清除垃圾收集算法在算法的标记阶段终止之前不允许回收数据。对于不允许破坏性操作的语言类,我们可以安排堆中的所有指针总是向后指向“旧”数据。在本文中,我们提出了一个简单的方案,用于使用单通道标记扫描收集器为此类语言类回收数据。我们还展示了如何修改简单方案,以便可以增量方式完成收集(使其适用于实时收集)。在此之后,我们展示了如何修改收集器以进行分代垃圾收集,最后展示了该方案如何用于具有并发进程的语言。1

于 2012-04-19T07:01:27.520 回答
18

Erlang 有一些特性使 GC 实际上非常容易。

1 - 每个变量都是不可变的,因此变量永远不能指向在它之后创建的值。

2 - 在 Erlang 进程之间复制值,因此进程中引用的内存几乎总是完全隔离的。

这两者(尤其是后者)都极大地限制了 GC 在收集期间必须扫描的堆数量。

Erlang 使用复制 GC。在 GC 期间,进程停止,然后将活动指针从 from-space 复制到 to-space。我忘记了确切的百分比,但是如果在收集过程中只能收集 ​​25% 的堆,那么堆将会增加,如果可以收集 75% 的进程堆,那么堆将会减少。当进程的堆满时会触发收集。

唯一的例外是发送到另一个进程的大值。这些将被复制到共享空间并被引用计数。当收集到共享对象的引用时,计数会减少,当该计数为 0 时,对象会被释放。不会尝试处理共享堆中的碎片。

这样做的一个有趣结果是,对于共享对象,共享对象的大小不会影响进程堆的计算大小,只有引用的大小会影响。这意味着,如果您有很多大型共享对象,您的 VM 可能会在触发 GC 之前耗尽内存。

大部分内容来自 Jesper Wilhelmsson 在 EUC2012 上的演讲。

于 2012-06-23T11:59:17.617 回答
5

我不知道你的背景,但除了 jj1bdx 已经指出的论文之外,你还可以给Jesper Wilhelmsson 论文一个机会。

顺便说一句,如果您想监控 Erlang 中的内存使用情况以将其与例如 C++ 进行比较,您可以查看:

希望这可以帮助!

于 2012-04-19T13:05:02.913 回答