Erlang垃圾回收机制的二三事
声明:本片文章是由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垃圾回收机制的二三事的更多相关文章
- .NET垃圾回收机制(二)
一.GC的必要性 1.应用程序对资源操作,通常简单分为以下几个步骤:为对应的资源分配内存 → 初始化内存 → 使用资源 → 清理资源 → 释放内存. 2.应用程序对资源(内存使用)管理的方式,常见的一 ...
- 深入了解Erlang 垃圾回收机制以及其重要性(转)
声明:本片文章是由Hackernews上的[Erlang Garbage Collection Details and Why ItMatters][1]编译而来,本着学习和研究的态度,进行的编译,转 ...
- Golang垃圾回收机制(二)
原文:https://blog.csdn.net/qq_15427331/article/details/54613635 Go语言正在构建的垃圾收集器(GC),似乎并不像宣传中那样的,技术上迎来了巨 ...
- 详解python的垃圾回收机制
python的垃圾回收机制 一.引子 我们定义变量会申请内存空间来存放变量的值,而内存的容量是有限的,当一个变量值没有用了(简称垃圾)就应该将其占用的内存空间给回收掉,而变量名是访问到变量值的唯一方式 ...
- day21 二十一、垃圾回收机制、re正则
一.内存管理 1.垃圾回收机制:不能被程序访问到的数据称之为垃圾 2.引用计数:引用计数是用来记录值的内存地址被记录的次数 每一次对值地址的引用都可以使该值的引用计数 +1 每一次对值地址的释放都可以 ...
- Erlang进程堆垃圾回收机制
原文:Erlang进程堆垃圾回收机制 作者:http://blog.csdn.net/mycwq 每一个Erlang进程创建之后都会有自己的PCB,栈,私有堆.erlang不知道他创建的进程会用到哪种 ...
- erlang二进制数据垃圾回收机制
erlang二进制数据在内存中有两种存在形式,当数据大小不到 64 bytes,就直接存在进程堆内.假设超过了64 bytes.就被保存到进程外的共享堆里,能够给节点内全部进程共享. erlang有两 ...
- 初识JVM:(二)Java的垃圾回收机制详解
声明:本文主要参考https://www.cnblogs.com/codeobj/p/12021041.html 仅供个人学习.研究之用,请勿用于商业用途,如涉及侵权,请及时反馈,立刻删除. 一.Ja ...
- .Net 垃圾回收机制原理(二)
英文原文:Jeffrey Richter 编译:赵玉开 链接http://www.cnblogs.com/yukaizhao/archive/2011/11/25/dot_net_GC_2.html ...
随机推荐
- 使用XSD校验Mybatis的SqlMapper配置文件(1)
这篇文章以前面对SqlSessionFactoryBean的重构为基础,先简单回顾一下做了哪些操作: 新建SqlSessionFactoryBean,初始代码和mybatis-spring相同: 重构 ...
- hibernate(九) 二级缓存和事务级别详讲
序言 这算是hibernate的最后一篇文章了,下一系列会讲解Struts2的东西,然后说完Struts2,在到Spring,然后在写一个SSH如何整合的案例.之后就会在去讲SSM,在之后我自己的个人 ...
- Android listview addHeaderView 和 addFooterView 详解
addHeaderView()方法:主要是向listView的头部添加布局addFooterView()方法:主要是向listView的底部添加布局 需要注意的是添加布局的时候应该添加从父容器开始添加 ...
- 《BI那点儿事》Microsoft 聚类分析算法——三国人物身份划分
什么是聚类分析? 聚类分析属于探索性的数据分析方法.通常,我们利用聚类分析将看似无序的对象进行分组.归类,以达到更好地理解研究对象的目的.聚类结果要求组内对象相似性较高,组间对象相似性较低.在三国数据 ...
- WebAPI 2.x中如何扩展Identity Store
ASP.NET WebAPI 中引入了新的一套身份验证和授权的机制,官方的叫法是ASP.NET Identity,有关这个概念的细节,感兴趣的同学可以参考 http://www.asp.net/ide ...
- 邻接表无向图(二)之 C++详解
本章是通过C++实现邻接表无向图. 目录 1. 邻接表无向图的介绍 2. 邻接表无向图的代码说明 3. 邻接表无向图的完整源码 转载请注明出处:http://www.cnblogs.com/skywa ...
- NSIS安装与向导生成代码
NSIS 的脚本语言和通常的编程语言有类似的结构和语法,但它是为安装程序这类应用所设计的.NSIS (Nullsoft Scriptable Install System) 是一个专业开源的制作 wi ...
- java加密解密的学习
注:此文章只是对如何学习java加密解密技术做一个讲解.并不涉及具体的知识介绍,如果有需要请留言,有时间我补冲长.个人觉着学习一个学习方法比学习一个知识点更有价值的多. 首先,对于加密解密知识体系没有 ...
- Windows Azure HandBook (4) 分析Windows Azure如何处理Session
<Windows Azure Platform 系列文章目录> 本文是对笔者之前的文章Windows Azure Cloud Service (13) 多个VM Instance场景下如何 ...
- Git:错误:error:src refspec master does not match any
新建立了一个远程仓库,想着把项目放上去.于是在项目目录上: git init 然后就添加远程库 git remote add origin xxxx.git 然后就想push: git push -u ...