每兩周一篇的連續(xù)更新來啦,雖然主要是為了完成社區(qū)任務 ??
Erlang Process 內(nèi)存分布
在了解 GC 之前影钉,我們先來看看 Erlang 進程的內(nèi)存分布是怎樣的:
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)纵顾、當前狀態(tài)(正在運行、等待间唉,等等)绞灼、注冊名稱、初始時和當前正在調(diào)用的函數(shù)呈野,等等低矮。PCB 還保存了一些指向傳入本進程的消息的指針,這些消息以鏈表的形式存儲在堆中被冒。
- Stack: 向下增長的內(nèi)存區(qū)域军掂,保存了函數(shù)入?yún)ⅰ⒑瘮?shù)調(diào)用返回地址昨悼、以及本地變量等等蝗锥。大一點的數(shù)據(jù)結(jié)構(gòu),比如 List 和 Tuple 這些是保存在 Heap 里的率触。
- Heap: 向上增長的內(nèi)存區(qū)域终议, 保存了進程的消息郵箱、復合的數(shù)據(jù)類型比如 Lists, Tuples and Binaries 以及像浮點數(shù)這種超過一個機器字(word)長度的對象。注意超過 64 個字節(jié)長度的 Binary 不是存儲在這個進程的私有 Heap 里的穴张,這種情況下進程的私有 Heap 里面存的是指向另一個全局共享 Heap 里 Binary 對象的指針细燎。這個共享 Heap 是可以被所有的 Process 訪問的,保存在全局共享 Heap 里面的“大 Binary” 叫做 Refc Binary (Reference Counted Binary)皂甘,而存在私有 Heap 里面的指針叫做 ProcBin玻驻。
每個 Erlang 進程都有自己的棧和堆,這些棧和堆分配在同一個內(nèi)存塊中偿枕,并"相向而行"??璧瞬。當棧和堆相遇時(也就是說此進程的內(nèi)存用盡時),將觸發(fā)垃圾回收益老。如果沒能回收足夠的內(nèi)存彪蓬,進程將會去申請更多的內(nèi)存。
我們來看一下進程的 PCB 里面的內(nèi)容:
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 的內(nèi)容捺萌,請注意 OTP 23 似乎刪掉了這個工具档冬,所以盡量用 OTP 22 之前的版本。
其中 htop
和 stop
是分別指向 Heap 和 Stack 頂部的指針桃纯,“頂部” 的意思是堆或者棧上的下一個可用的內(nèi)存片段酷誓。heap
和 hend
指針分別指向整個 Heap 的起始和終止位置。然后 heap_size
字段是 Heap 的大小态坦,單位是字 (words)盐数。就是說,在 64 位機器上:hend - heap = heap_sz * 8
伞梯,32 位機器上:hend - heap = heap_sz * 4
玫氢。最后要說的就是 min_heap_size = 0xe9
(words),這個指的是 Heap 的初始大小谜诫,同時也是當 Erlang 進程內(nèi)存收縮的時候所允許的 Heap 最小值漾峡,默認值是 0xe9(233)
。
需要注意的是喻旷,前面我們介紹的 Heap 和 Stack 兩個概念生逸,其實在 Erlang 進程的內(nèi)存里是同一塊內(nèi)存,heap
和 hend
兩個指針分別指向了這塊內(nèi)存的開始和終止位置且预。這也是為什么上面 hipe_bifs:show_pcb/1
的結(jié)果里槽袄,對于堆棧只有個 stop
指針而沒有對應的 stack
和 send
》嫘常基于這些我們再重新畫一下文章開始的內(nèi)存圖:
+--+-------------------------------+ <--- *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 低地址
現(xiàn)在我們可以簡單的介紹一下垃圾回收了遍尺。
簡單地將,Erlang 的垃圾回收是一個分代的垃圾回收怀估,并且是基于單進程的狮鸭,所以它不會 Stop the world
合搅。從上面我們了解到的 Erlang 進程內(nèi)存結(jié)構(gòu)來說,它是工作在進程的私有 Heap 之上(從 *heap 到 *hend)歧蕉,然后額外還要去清掃 ProcBin 指向的全局共享 Heap 中的那些內(nèi)存灾部。
關于 Erlang 垃圾回收的進一步的細節(jié),我們將放到本系列的后續(xù)文章中講解惯退。
引用文獻以及資料: