声明:本片文章是由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的内存模型文档建议你“如果你必须要通过阅读剩下的文档去了解你的编程的行为,那么你太聪明了。不要这么聪明”,我相信我们必须要足够的聪明去让我们的系统运行得更快更安全,但做到这一点,深入了解它的原理是必不可少的。

转自: 云巴专栏 https://segmentfault.com/a/1190000003758525

深入了解Erlang 垃圾回收机制以及其重要性(转)的更多相关文章

  1. Erlang垃圾回收机制的二三事

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

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

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

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

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

  4. .net垃圾回收机制编程调试试验

    1. 什么是CLR GC? 它是一个基于引用跟踪和代的垃圾回收器. 从本质上,它为系统中所有活跃对象都实现了一种引用跟踪模式,如果一个对象没有任何引用指向它,那么这个对象就被认为是垃圾对象,并且可以被 ...

  5. JavaScript具有自动垃圾回收机制

    JavaScript具有自动垃圾回收机制 原理: 找出那些不再继续使用的变量,然后释放其占用的内存.   正常的生命周期:     局部变量指在函数执行的过程中存在.而在这个过程中,会为局部变量在栈或 ...

  6. java垃圾回收机制

    1 .垃圾回收机制(GC)垃圾回收就是回收内存中不再使用对象:(1)垃圾回收的步骤:1)查找内存中不再使用的对象:2)释放这些对象所占用的内存:(2)查找内存中不再使用的对象方法:1)引用计数法如果一 ...

  7. 垃圾回收机制GC知识再总结兼谈如何用好GC

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

  8. 【转载】Java垃圾回收机制

    原文地址:http://www.importnew.com/19085.html Java垃圾回收机制 说到垃圾回收(Garbage Collection,GC),很多人就会自然而然地把它和Java联 ...

  9. 【转】深入理解 Java 垃圾回收机制

    深入理解 Java 垃圾回收机制   一.垃圾回收机制的意义 Java语言中一个显著的特点就是引入了垃圾回收机制,使c++程序员最头疼的内存管理的问题迎刃而解,它使得Java程序员在编写程序的时候不再 ...

随机推荐

  1. 基于avalon+jquery做的bootstrap分页控件

    刚开始学习avalon,项目需要就尝试写了个分页控件Pager.js:基于BootStrap样式这个大家都很熟悉 在这里推荐下国产前端神器avalon:确实好用,帮我解决了很多前端问题. 不多说了,代 ...

  2. SQL Server 获取日期是星期周几(默认从周日开始到周六 1-7)

    ALTER FUNCTION [dbo].[GetWeekDay] ( @Year INT, @Month INT ,@Day INT ) RETURNS INT AS BEGIN DECLARE @ ...

  3. Android获取视频音频的时长的方法

    android当中获取视频音频的时长,我列举了三种. 1:获取视频URI后获取cursor cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore ...

  4. linux bash 入门

    #!/bin/bash #shell使用的熟练成都反映用户对Unix/Linux使用的熟练程度 #shell 有两种执行命令的方式:交互式和批处理 #常见的shell脚本解释器有bash,sh,csh ...

  5. 批处理学习:for语句详解

    大纲 一 前言 二 for语句的基本用法 三 for /f (delims.tokens.skip.eol.userbackq.变量延迟) 四 for /r (递归遍历) 五 for /d (遍历目录 ...

  6. DTD 和 Schema简介

    什么是DTD? DTD(文档类型定义)的作用是定义 XML 文档的合法构建模块. 它使用一系列的合法元素来定义文档结构. DTD例子 <?xml version="1.0"? ...

  7. postman(一)批量执行接口测试用例

    postman(一)批量执行接口测试用例 学习了:https://blog.csdn.net/github_36032947/article/details/78611405 还可以把collecti ...

  8. Web项目中用mybatis配置多个数据库

    需要在项目中配置多个数据库(比如一个mysql,一个oracle)的时候,可按照如下方式配置 首先是第一个数据库的配置 <bean name="transactionManager&q ...

  9. [GraphQL] Reuse Query Fields with GraphQL Fragments

    A GraphQL fragment encapsulates a collection of fields that can be included in queries. In this vide ...

  10. C#如何改变字符串编码

    public string UTF8ToGB2312(string str)        {            try            {                    Encod ...