Erlang垃圾收集细节及其原因(2015)

2021-03-23 17:05:13

Erlang尝试解决的主要问题之一是创建一个实现具有高响应级别的软实时系统的平台。这种系统需要快速的垃圾收集机制,不能停止系统及时响应。当我们认为Erlang作为一种不可变形的语言时,垃圾收集更加重要,因为具有非破坏性更新属性,因为这些语言具有很高的生产垃圾。

在挖掘GC之前,必须检查Erlang进程的内存布局,可分为三个主要部分:过程控制块,堆栈和堆。它与UNIX进程内存布局类似。

共享堆erlang过程内存布局+ ---------------------------------------- ------------------------- + | | | | | | | PID /状态/注册名称|过程| | | |控制| | |初始呼叫/当前呼叫+ ---->块| | | | (PCB)| | |邮箱指针| | | | | | | + ------------------------------- + | | | | | | |功能参数| | | | |过程| | |返回地址+ ---->堆栈| | | | | + -------------- + | |局部变量| | | | | | | | | + ------------ + - + | + ---------------------------- + - + | | | | | | | | | | | + ------------- + - + | | ^ V + ---->自由| | | | | | | | |空间| | | | + -------------- + - + | + - + ---------------------------- + | + - + | | | | | | | + - + | REFC二进制| | |邮箱消息(链接列表)| | + - + | | | | | + ------ ^ ---------- + | |复合术语(名单,元组)|过程| | | | + ---->私人| | | |术语大于单词|堆| | | | | | + - + procbin + ------------- +指向大型二进制文件| | | | | + --------------------------------------------- --------------------

PCB:Process Control块在过程表中包含有关其标识符(PID)等过程的一些信息,当前状态(运行,等待),其注册名称,初始和当前呼叫以及PCB对传入消息的指向是存储在堆中的链接列表的成员。

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

堆:它是一个向上生长的内存区域,它包含流程邮箱的物理信息,比如列表,元组和二进制文件,如诸如浮点数的机器单词的列表,元组和二进制文件和对象。大于64字节的二进制术语不存储在进程私有堆中。它们被称为Refc二进制(参考计数二进制),并存储在一个大的共享堆中,该堆可通过具有该Refc二进制文件指针的所有进程访问。该指针称为ProcBin,并存储在进程私有堆中。

为了简明扼要地解释当前默认的erlang的GC机制;它是一个世代复制垃圾收集,可以独立地在每个erlang进程私有堆内运行,并且还针对全局共享堆出现引用计数垃圾收集。

私人堆的GC是世代的。世代GC将堆分为两段:年轻人和旧代。这种分离是基于这样一个事实:如果物体存活到GC循环,它在短期内将成为垃圾的可能性很低。因此,年轻一代是用于新分配的数据,旧一代是幸存于实施特定数量的GC的数据。这种分离有助于GC减少其在没有成为垃圾的数据上的不必要的周期。在Erlang垃圾收集的背景下有两种策略;世代(次要的)和古代(专业)。世代GC只收集了年轻的堆,但填充了旧堆。现在让我们在新开始的Erlang进程的私人堆中查看GC步骤:

没有GC在短期过程中发生,该过程不再使用min_heap_size然后终止堆。这样收集过程中使用的整个内存。

一种新生产的进程,其数据增加了Min_heap_size使用FullSweep GC,显然是因为尚未发生GC,因此对Offer和Old TewNations的对象之间没有分离。之后,首次全普通GC之后,堆分为年轻人和旧段,然后将GC策略交换为代理,并在进程终止之前保持在其上。

当GC策略再次从代域切换到Fullsweep时,有一个过程寿命。第一个案例是经过一定数量的世代GC。该某个号码可以全局或使用FullSweep_After标志指定或每个进程指定。此外,Foursweep GC之前的世代GC的计数器和其上限分别是Minor_GCS和Fullsweep_After属性,并且可以在Process_Info(PID,Garbage_Collection)的返回值中看到。第二种情况是当生成GC不能收集足够的内存并且最后一个情况是手动调用垃圾箱(PID)函数时。在这些情况之后,GC策略再次从FullSweep再次恢复到生成,并留在上面发生的情况下。

Spawn> Fullsweep>世代> Fullsweep>增加堆和gt; Fullsweep> ...>终止

在方案3中,如果第二次_ ullsweep gc无法收集足够的内存,则堆大小增加,GC策略再次切换到ullsweep,如新生生成的过程,并且可以再次又一次地发生所有这四种情况。

现在,问题是为什么它在自动垃圾收集的语言中很重要,就像Erlang一样。过度地,这种知识可以帮助您通过调整全球范围或每个过程的GC发生和策略来使您的系统变得更快。其次,我们可以理解从其垃圾收集的角度来使Erlang成为软实时平台的主要原因之一。这是因为每个进程都有自己的私人堆和自己的GC,所以每次GC都会发生在一个过程中它只是停止了被收集的Erlang进程,但不会停止其他进程,这是一个软实时系统需要。

共享堆的GC是参考计数。共享堆(REFC)中的每个对象都具有由存储在Erlang进程的私有堆内的其他对象(Procbin)所持的IT引用的反击。如果对象的参考计数器达到零,则对象已无法访问,并将被销毁。 Count Counting GC非常便宜,有助于系统避免意外的长时间停顿并提高系统响应性。但是,在设计演员模型系统时不知道一些着名的反模式可能会在内存泄漏时发出麻烦。

首先,当REFC分成分二进制时。为了便宜;子二进制文件不是原始二进制文件的拆分部分的新副本,但只是参考该部分。然而,除了原始二进制文件之外,这个子二进制数量还是一个新的参考,而且你知道,当原始二进制文件必须挂起时,它可能会导致问题才能收集其子二进制。

其他已知问题是当存在一种用于控制和传送大型REFC二进制消息的请求控制器或消息路由器时的一种长寿命的中间件进程。由于此过程触及每个REFC消息,因此它们的计数器增量。因此,收集这些REFC消息取决于收集中间件内部的所有PROCBIN对象。不幸的是,因为普罗氏只是一个指针,因此它们非常便宜,可能需要很长时间才能发生中间件进程内的GC。因此,即使从中间件从所有其他进程收集,REFC消息也保持在共享堆上。

共享堆重要事项,因为它会减少在进程之间传递大型二进制消息的IO。创建子二进制文件也很快,因为它们只是指向另一个二进制文件的指针。但是,由于拇指的规则,使用快捷方式更快地拥有成本,其成本是以不困在不良条件的方式陷入困境的方式。还有一些众所周知的建筑模式,用于REFC二进制泄漏问题,FRED Hebbert在他的免费电子书中解释了它们; Erlang愤怒,我认为我无法比他更好地解释它。所以我强烈建议你读它。

即使我们使用像Erlang这样的内存本身的语言,也没有任何东西可以防止我们了解如何分配和删除的内存。与Go Language Memory Model文档页面不同,“如果您必须阅读本文档的其余部分以了解程序的行为,您将太聪明。不要聪明。“,我相信我们必须聪明地使我们的系统更快,更安全,有时它不会发生,除非我们深入进入发生的事情。