Erlang 垃圾回收机制(一)

每两周一篇的连续更新来啦,虽然主要是为了完成社区任务 😂

Erlang Process 内存分布

在了解 GC 之前,我们先来看看 Erlang 进程的内存分布是怎样的:

          Shared Heap                        Erlang Process Memory Layout                  
                                                                                             
 +----------------------------------+      +----------------------------------+              
 |                                  |      |                                  |              
 |                                  |      |  PID / Status / Registered Name  |       Process
 |                                  |      |                                  |       Control
 |                                  |      |   Initial Call / Current Call    +---->  Block  
 |                                  |      |                                  |       (PCB)  
 |                                  |      |         Mailbox Pointers         |              
 |                                  |      |                                  |              
 |                                  |      +----------------------------------+              
 |                                  |      |                                  |              
 |                                  |      |        Function Parameters       |              
 |                                  |      |                                  |       Process
 |                                  |      |         Return Addresses         +---->  Stack  
 |                                  |      |                                  |              
 |    +--------------+              |      |         Local Variables          |              
 |    |              |              |      |                                  |              
 |    | +------------+--+           |      +-------------------------------+--+              
 |    | |               |           |      |                               |  |              
 |    | | +-------------+--+        |      |  ^                            v  +---->  Free   
 |    | | |                |        |      |  |                               |       Space  
 |    | | | +--------------+-+      |      +--+-------------------------------+              
 |    +-+ | |                |      |      |                                  |              
 |      +-+ |  Refc Binary   |      |      |  Mailbox Messages (Linked List)  |              
 |        +-+                |      |      |                                  |              
 |          +------^---------+      |      |  Compound Terms (List, Tuples)   |       Process
 |                 |                |      |                                  +---->  Private
 |                 |                |      |     Terms Larger than a word     |       Heap   
 |                 |                |      |                                  |              
 |                 +--+ ProcBin +-------------+ Pointers to Large Binaries    |              
 |                                  |      |                                  |              
 +----------------------------------+      +----------------------------------+              
  • PCB: 进程控制块保存一些有关进程的信息,如进程标识符(PID)、当前状态(正在运行、等待,等等)、注册名称、初始时和当前正在调用的函数,等等。PCB 还保存了一些指向传入本进程的消息的指针,这些消息以链表的形式存储在堆中。
  • Stack: 向下增长的内存区域,保存了函数入参、函数调用返回地址、以及本地变量等等。大一点的数据结构,比如 List 和 Tuple 这些是保存在 Heap 里的。
  • Heap: 向上增长的内存区域, 保存了进程的消息邮箱、复合的数据类型比如 Lists, Tuples and Binaries 以及像浮点数这种超过一个机器字(word)长度的对象。注意超过 64 个字节长度的 Binary 不是存储在这个进程的私有 Heap 里的,这种情况下进程的私有 Heap 里面存的是指向另一个全局共享 Heap 里 Binary 对象的指针。这个共享 Heap 是可以被所有的 Process 访问的,保存在全局共享 Heap 里面的“大 Binary” 叫做 Refc Binary (Reference Counted Binary),而存在私有 Heap 里面的指针叫做 ProcBin

每个 Erlang 进程都有自己的栈和堆,这些栈和堆分配在同一个内存块中,并"相向而行"🐶。当栈和堆相遇时(也就是说此进程的内存用尽时),将触发垃圾回收。如果没能回收足够的内存,进程将会去申请更多的内存。

我们来看一下进程的 PCB 里面的内容:

1> hipe_bifs:show_pcb(self()).
P: 0x00007f7f3cbc0400
---------------------------------------------------------------
Offset| Name | Value              | *Value |
    0 | id   | 0x000001d0000003a3 | |
   72 | htop | 0x00007f7f33f15298 | |
   96 | hend | 0x00007f7f33f16540 | |
   88 | heap | 0x00007f7f33f11470 | |
  104 | heap_sz | 0x0000000000000a1a | |
   80 | stop | 0x00007f7f33f16480 | |
  592 | gen_gcs | 0x0000000000000012 | |
  594 | max_gen_gcs | 0x000000000000ffff | |
  552 | high_water | 0x00007f7f33f11c50 | |
  560 | old_hend | 0x00007f7f33e90648 | |
  568 | old_htop | 0x00007f7f33e8f8e8 | |
  576 | old_head | 0x00007f7f33e8e770 | |
  112 | min_heap_size | 0x00000000000000e9 | |
  328 | rcount | 0x0000000000000000 | |
  336 | reds | 0x0000000000002270 | |
  16  | tracer | 0xfffffffffffffffb | |
  24  | trace_fla.. | 0x0000000000000000 | |
  344 | group_lea.. | 0x0000019800000333 | |
  352 | flags | 0x0000000000002000 | |
  360 | fvalue | 0xfffffffffffffffb | |
  368 | freason | 0x0000000000000000 | |
  320 | fcalls | 0x00000000000005a2 | |
  384 | next | 0x0000000000000000 | |
  48  | reg | 0x0000000000000000 | |
  56  | nlinks | 0x00007f7f3cbc0750 | |
  616 | mbuf | 0x0000000000000000 | |
  640 | mbuf_sz | 0x0000000000000000 | |
  464 | dictionary | 0x0000000000000000 | |
  472 | seq..clock | 0x0000000000000000 | |
  480 | seq..astcnt | 0x0000000000000000 | |
  488 | seq..token | 0xfffffffffffffffb | |
  496 | intial[0] | 0x000000000000320b | |
  504 | intial[1] | 0x0000000000000c8b | |
  512 | intial[2] | 0x0000000000000002 | |
  520 | current | 0x00007f7f3be87c20 | 0x000000000000ed8b |
  296 | cp | 0x00007f7f3d3a5100 | 0x0000000000440848 |
  304 | i | 0x00007f7f3be87c38 | 0x000000000044353a |
  312 | catches | 0x0000000000000001 | |
  224 | arity | 0x0000000000000000 | |
  232 | arg_reg | 0x00007f7f3cbc04f8 | 0x000000000000320b |
  240 | max_arg_reg | 0x0000000000000006 | |
  248 | def..reg[0] | 0x000000000000320b | |
  256 | def..reg[1] | 0x0000000000000c8b | |
  264 | def..reg[2] | 0x00007f7f33ec9589 | |
  272 | def..reg[3] | 0x0000000000000000 | |
  280 | def..reg[4] | 0x0000000000000000 | |
  288 | def..reg[5] | 0x00000000000007d0 | |
  136 | nsp | 0x0000000000000000 | |
  144 | nstack | 0x0000000000000000 | |
  152 | nstend | 0x0000000000000000 | |
  160 | ncallee | 0x0000000000000000 | |
  56  | ncsp | 0x0000000000000000 | |
  64  | narity | 0x0000000000000000 | |
---------------------------------------------------------------

我们使用 hipe_bifs:show_pcb 来打印 PCB 的内容,请注意 OTP 23 似乎删掉了这个工具,所以尽量用 OTP 22 之前的版本。

其中 htopstop 是分别指向 Heap 和 Stack 顶部的指针,“顶部” 的意思是堆或者栈上的下一个可用的内存片段。heaphend 指针分别指向整个 Heap 的起始和终止位置。然后 heap_size 字段是 Heap 的大小,单位是字 (words)。就是说,在 64 位机器上:hend - heap = heap_sz * 8 ,32 位机器上:hend - heap = heap_sz * 4 。最后要说的就是 min_heap_size = 0xe9(words),这个指的是 Heap 的初始大小,同时也是当 Erlang 进程内存收缩的时候所允许的 Heap 最小值,默认值是 0xe9(233)

需要注意的是,前面我们介绍的 Heap 和 Stack 两个概念,其实在 Erlang 进程的内存里是同一块内存,heaphend 两个指针分别指向了这块内存的开始和终止位置。这也是为什么上面 hipe_bifs:show_pcb/1 的结果里,对于堆栈只有个 stop 指针而没有对应的 stacksend。基于这些我们再重新画一下文章开始的内存图:

+--+-------------------------------+ <--- *hend              高地址
|                                  |              
|        Function Parameters       |              
|                                  |       Process
|         Return Addresses         +---->  Stack  
|                                  |              
|         Local Variables          |              
|                                  |              
+-------------------------------+--+ <--- *stop         
|                               |  |              
|  ^                            v  +---->  Free   
|  |                               |       Space  
+--+-------------------------------+              
|                                  |              
|  Mailbox Messages (Linked List)  |              
|                                  |              
|  Compound Terms (List, Tuples)   |       Process
|                                  +---->  Private
|     Terms Larger than a word     |       Heap   
|                                  |              
---+ Pointers to Large Binaries    |              
|                                  |              
+----------------------------------+ <--- *heap             低地址

现在我们可以简单的介绍一下垃圾回收了。

简单地将,Erlang 的垃圾回收是一个分代的垃圾回收,并且是基于单进程的,所以它不会 Stop the world。从上面我们了解到的 Erlang 进程内存结构来说,它是工作在进程的私有 Heap 之上(从 *heap 到 *hend),然后额外还要去清扫 ProcBin 指向的全局共享 Heap 中的那些内存。

关于 Erlang 垃圾回收的进一步的细节,我们将放到本系列的后续文章中讲解。

引用文献以及资料:

  1. http://erlang.org/doc/apps/erts/GarbageCollection.html

  2. https://hamidreza-s.github.io/erlang%20garbage%20collection%20memory%20layout%20soft%20realtime/2015/08/24/erlang-garbage-collection-details-and-why-it-matters.html

  3. https://github.com/happi/theBeamBook

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,189评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,577评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,857评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,703评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,705评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,620评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,995评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,656评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,898评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,639评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,720评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,395评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,982评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,953评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,195评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,907评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,472评论 2 342

推荐阅读更多精彩内容