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 ...
随机推荐
- 1.1.1. Atitit Cocos2d-JS v3.x的问题
1.1.1. Atitit Cocos2d-JS v3.x的问题 1.1. Api 兼容性 场景切换的api都不兼容了... 删除了比较多的api...可以dep啊.. 1.2. gui布局,, ...
- struts2标签
一.通用标签 1.property Name Required Default Evaluated Type Description default false false String ...
- hdu2066一个人的旅行(多源点多汇点的最短路径问题)
/* 思路:多源点,多会点的最短路径! 将最小号-1的节点但最源点,将最大号+1的点当作汇点! 将问题转变成从一个源点到一个汇点的最短路径的问题! 开始忘记初始化vector了,哇了好多次....坑爹 ...
- Oracle身份认证方式
Oracle对于普通账户和超级管理员(指sysdba和sysoper)的认证机制不一样,前者是通过数据字典,后者主要是通过操作系统验证和密码文件验证.因此一般提到操作系统认证或密码文件认证,针对的都是 ...
- Java多线程系列--“JUC线程池”03之 线程池原理(二)
概要 在前面一章"Java多线程系列--“JUC线程池”02之 线程池原理(一)"中介绍了线程池的数据结构,本章会通过分析线程池的源码,对线程池进行说明.内容包括:线程池示例参考代 ...
- java之Maven配置和springMvc的简单应用
初始springMvc这个框架,非常的陌生,而且幸好公司是通过maven这个代码管理工具,可以随时添加依赖.解决了很多问题在以后深入开发中. 项目结构: 通过结构中,pom.xml这个文件其实就说明这 ...
- Django快速开发之投票系统
https://docs.djangoproject.com/en/1.8/intro/tutorial01/ 参考官网文档,创建投票系统. ================ Windows 7/1 ...
- 3D Grid Effect – 使用 CSS3 制作网格动画效果
今天我们想与大家分享一个小的动画概念.这个梦幻般的效果是在马库斯·埃克特的原型应用程序里发现的.实现的基本思路是对网格项目进行 3D 旋转,扩展成全屏,并呈现内容.我们试图模仿应用程序的行为,因此 ...
- MAC Objective-C 开发经典书籍推荐
MAC Objective-C 开发经典书籍推荐 闻道有先后,术业有专攻,这句话放到计算机科学领域的理解可能每个人都会不同. 有些人选择一个操作系统,一个体系的编程语言,作一个领域的开发. 有些人选择 ...
- UI/UE/ID/UED/UCD的区别
简述: UI (User Interface):用户界面 UE (User Experience):用户体验 ID (Interaction design):交互设计 UID (User Interf ...