声明:本片文章是由Hackernews上的[Erlang Garbage Collection Details and Why It
Matters][1]编译而来,本着学习和研究的态度,进行的编译,转载请注明出处。

Erlang需要解决的重要问题之一就是为实现极高响应能力的软实时系统创建平台。这样的系统需要一个快速的垃圾回收机制,而这个机制不会阻止系统及时的响应。另一方面,当我们把Erlang看作一种用无损更新属性的不可改变语言时,这个垃圾回收机制就显得更加重要了,因为这种语言有很高的几率产生垃圾。

内存布局

在深入了解GC之前,有一个很重要的事,就是检查Erlang过程的内存布局的三个重要的点:进程控制模块,栈和堆。它和Unix的内存布局非常的相像。

进程控制模块:进程控制模块会保存一些关于进程的信息比如它在进程表中的标识符(PID)、当前状态(运行、等待)、它的注册名、初始和当前调用,同时PCB也会保存一些指向传入消息的指针,这些传入消息是存储在堆中连接表中的。

栈:它是一个向下生长的存储区,这个存储区保存输入和输出参数、返回地址、本地变量和用于evaluating expressions的临时空间。

堆:它是一个向上生长的存储区,这个存储区保存进程邮箱的物理消息,像列表、元组和Binaries这种的复合项以及比像浮点数这种一个机器字更大的对象。超过64机器字的二进制项不会存储在进程私有堆里。他们被称作Refc Binary (Reference Counted Binary)并被存储在一个大的共享堆里,只要有那个Refc Binary指针的进程都可以访问这个堆。这个储存在进程私有堆中的指针叫作ProcBin。

GC的细节

为了结实当前默认Erlang的GC机制,简单的说,它是一个分代复制的垃圾回收,独立运行在每个Erlang进程私有堆的内部,而且它也是发生在全球共享堆中的引用计数垃圾回收。

私有堆GC

私有堆的GC是分代的。分代GC把堆分为了新生和老年代两个部分。如果一个对象在GC循环生存下来,那么它在短期内成为垃圾的几率将会很低,这也是这个划分的依据所在。因此,新生代是为新分配的数据准备的,老年代是为了在数次GC启动后生存下来数据的。这个分代帮助了GC减少在还没有成为垃圾数据上的不必要的循环。对于Erlang垃圾回收有两个策略:Generational (Minor)和Fullsweep (Major)。分代的GC只收集新生的堆,而fullsweep的堆新老都会收集。现在,让我们回顾一个新开始Erlang进程私有堆的GC步骤:

场景1:

Spawn > No GC > Terminate

如果一个短暂的进程没有使用超过min_heap_size的堆就结束了,GC是不会发生的。这种情况下所有被进程使用过的内存会被收集。

场景2:

Spawn > Fullsweep > Generational > Terminate

如果一个新生产的进程的数据增长超过min_heap_size,那么会使用fullsweep GC,显然这是因为没有GC发生,那么也不会有新生代和老年代之分。在第一次fullsweep GC后,堆就会被分代成这两部分,之后GC策略会转化到分代并保持到进程结束。

场景3:

Spawn > Fullsweep > Generational > Fullsweep > Generational > ... >
Terminate

有几种情况,GC策略在进程过程中由分代转化回到fullsweep。第一种情况是进过一定次数的分代GC。这个数量可以是特定全局的或者是每个有fullsweep_after flag的进程。同时在fullsweep GC之前每个的进程和它的上限的分代GC计数器分别是minor_gcs 和 fullsweep_after特性,并在process_info(PID, garbage_collection)返回值中可见。第二种情况是当分代GC不能收集到足够的内存,最后一种情况是garbage_collect(PID)函数被手动调用。在这些情况后,GC策略会回复到从fullsweep到分代然后保持直到上述情形发生。

场景4:

Spawn > Fullsweep > Generational > Fullsweep > Increase Heap >
Fullsweep > ... > Terminate

在场景3中,如果第二fullsweep GC不能收集到足够内存,堆的大小会增加,GC策略又会转化成fullsweep,就像新生成的进程一样,这四种场景可以不断的出现。

现在的问题是为什么在像Erlang这种自动垃圾收集语言这么重要。首先这些知识可以帮助你通过调整GC的发生和策略使你的系统运行更快。其次,这是我们明白从垃圾回收的角度使Erlang变成软件实时平台的重要原因的地方。这是因为每个进程都有它自己的堆和它自己的GC,所以每次GC出现在一个进程中的时候,只是停止正在收集过程中的Erlang进程,但不会停止其他的进程,而这正是一个软实时系统所需要的。

共享堆GC

共享堆的GC是参考计数。每个在共享堆(Refc)的对象都有与存储的其他对象(ProcBin)相对的参考计数器,这些其他对象(ProcBin)都存储在Erlang进程私有堆内部。如果一个对象参考计数器达到0,这个对象会变得无法访问并将销毁。参考计数器很廉价并且可以帮助系统避免意外长时的暂停而且提高体统的响应速度。但是在设计你的actor模型系统时,不了解一些著名的反模式会导致一些问题,比如内存泄漏

当Refc第一次分成一个Sub-Binary。为了降低成本,一个sub-binary不是一个原binary分裂部分的新副本,仅仅是那个部分的一个参考。然而这个sub-binary会被当作加入到原binary的的一个新的参考,你知道,当原binary必须挂在它的sub-binary上时,这可能会引起一些问题。

其他已知的问题会发生在当一种生命周期很长的中间件当作控制和传递大型Refc binary消息的请求控制器或消息路由器时。当这个进程接触到每个Refc消息时,它们的计数器会递增。因此收集这些Refc消息依靠于收集所有ProcBin对象,即使它们在中间件进程中。不幸的是,因为ProcBin仅仅只是个指针,因此它们成本很低而且在中间件进程中需要花很长的时间去触发GC。所以即使已经从除了中间件其他所有进程中收集了Refc消息,它们也需要保留在共享堆里。

共享堆之所以重要是因为它减少了由于在进程之间传递大量binary消息的IO。由于sub-binaries仅仅是其他binary的指针,他们可以快速的创建。但是作为一种经验法则,使用变得更快的捷径会产生成本,这个成本会以一种不会在恶劣条件下困住方式去构建你的系统。同时也有很多应对Refc binary泄露的著名方法,比如Fred Hebert在他的ebook发表的Erlang in Anger。我认为我不能解释的比他更好,所以强烈推荐你去阅读。

总结:

即使我们使用像Erlang这种自我管理内存的语言,了解内存是如何分配和释放也是很必要的。不像Go的内存模型文档建议你“如果你必须要通过阅读剩下的文档去了解你的编程的行为,那么你太聪明了。不要这么聪明”,我相信我们必须要足够的聪明去让我们的系统运行得更快更安全,但做到这一点,深入了解它的原理是必不可少的。

资料:

• Academic and Historical Questions about Erlang
• Implementation of FPL & Concurrency
• Efficient Memory Management for Message-Passing Concurrency Paper
• Programming the Parallel World by Erlang Paper

关于Erlang内存泄漏的问题的一些分析可以参见云巴之前的一篇Erlang内存泄漏分析的文章
有什么问题欢迎留言交流。

Erlang垃圾回收机制的二三事的更多相关文章

  1. .NET垃圾回收机制(二)

    一.GC的必要性 1.应用程序对资源操作,通常简单分为以下几个步骤:为对应的资源分配内存 → 初始化内存 → 使用资源 → 清理资源 → 释放内存. 2.应用程序对资源(内存使用)管理的方式,常见的一 ...

  2. 深入了解Erlang 垃圾回收机制以及其重要性(转)

    声明:本片文章是由Hackernews上的[Erlang Garbage Collection Details and Why ItMatters][1]编译而来,本着学习和研究的态度,进行的编译,转 ...

  3. Golang垃圾回收机制(二)

    原文:https://blog.csdn.net/qq_15427331/article/details/54613635 Go语言正在构建的垃圾收集器(GC),似乎并不像宣传中那样的,技术上迎来了巨 ...

  4. 详解python的垃圾回收机制

    python的垃圾回收机制 一.引子 我们定义变量会申请内存空间来存放变量的值,而内存的容量是有限的,当一个变量值没有用了(简称垃圾)就应该将其占用的内存空间给回收掉,而变量名是访问到变量值的唯一方式 ...

  5. day21 二十一、垃圾回收机制、re正则

    一.内存管理 1.垃圾回收机制:不能被程序访问到的数据称之为垃圾 2.引用计数:引用计数是用来记录值的内存地址被记录的次数 每一次对值地址的引用都可以使该值的引用计数 +1 每一次对值地址的释放都可以 ...

  6. Erlang进程堆垃圾回收机制

    原文:Erlang进程堆垃圾回收机制 作者:http://blog.csdn.net/mycwq 每一个Erlang进程创建之后都会有自己的PCB,栈,私有堆.erlang不知道他创建的进程会用到哪种 ...

  7. erlang二进制数据垃圾回收机制

    erlang二进制数据在内存中有两种存在形式,当数据大小不到 64 bytes,就直接存在进程堆内.假设超过了64 bytes.就被保存到进程外的共享堆里,能够给节点内全部进程共享. erlang有两 ...

  8. 初识JVM:(二)Java的垃圾回收机制详解

    声明:本文主要参考https://www.cnblogs.com/codeobj/p/12021041.html 仅供个人学习.研究之用,请勿用于商业用途,如涉及侵权,请及时反馈,立刻删除. 一.Ja ...

  9. .Net 垃圾回收机制原理(二)

    英文原文:Jeffrey Richter 编译:赵玉开 链接http://www.cnblogs.com/yukaizhao/archive/2011/11/25/dot_net_GC_2.html ...

随机推荐

  1. atitit.Servlet2.5 Servlet 3.0 新特性 jsp2.0 jsp2.1 jsp2.2新特性

    atitit.Servlet2.5 Servlet 3.0 新特性 jsp2.0 jsp2.1 jsp2.2新特性   1.1. Servlet和JSP规范版本对应关系:1 1.2. Servlet2 ...

  2. 常用Math 方法

    /** * * @authors Your Name (you@example.org) * @date 2016-11-18 11:26:44 * @version $Id$ */ Math.pow ...

  3. Java——List集合

    package om.hanqi.test; import java.util.ArrayList; import java.util.List; public class Test01 { publ ...

  4. ListView优化为何ViewHolder用static类(转载)

    如果有人还不了解ViewHolder为什么可以起到优化作用,我这边再做下简单说明:Android的findViewById动作是比较耗时的,需要遍历布局的树形结构,才能找到相应的视图.所以如果想在这一 ...

  5. CSS中模拟父元素选择器

    很多情况下,我们需要找到父元素,但可惜的是css中并没有这样的一个选择器. 至于原因可以看张鑫旭的如何在CSS中实现父选择器效果这篇文章. 简单来说这个实现并不是真正的父元素选择器,只是利用其它思路来 ...

  6. python类定义与c#的一些区别

    c#中可以定义一个空类,但是python中定义空类需要加pass class EmptyClass(object): pass python的lei是多继承   python子类继承了基类,如果子类也 ...

  7. 哈夫曼树(二)之 C++详解

    上一章介绍了哈夫曼树的基本概念,并通过C语言实现了哈夫曼树.本章是哈夫曼树的C++实现. 目录 1. 哈夫曼树的介绍 2. 哈夫曼树的图文解析 3. 哈夫曼树的基本操作 4. 哈夫曼树的完整源码 转载 ...

  8. mybatis generator配置生成代码的问题

    接触第二种orm两天下来,一脸懵逼.mybatis是大多数公司所推崇的,相比于hibernate性能较为好的,操作更为方便的轻量级工具,所以小富就搞起这个orm.好吧,都说mybatis有个配置可以自 ...

  9. Python - Tips

    01 - input与raw_input的区别 input() #可以直接输入数字,但输入字符的要用引号''或者双引号"" raw_input() #将所有的输入都直接当作一串字符 ...

  10. [C] C语言中的布尔值

    C不具备显示的布尔类型,所以使用整数来代替,规则是:零是假,任何非零值皆为真. 反过来说,如果逻辑表达式为真其值一定为真,若逻辑表达式为假其值一定为零.