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

你可能感兴趣的:(Erlang 垃圾回收机制(一))