Erlang 的process 是虚拟机层面的进程,每个Erlang process 都包括一个 pcb(process control block), 一个stack 以及私有heap .

这部分的姿势, 在各种论文中都有提到. 网上也有各种各样的解读,包括但不仅限于:

1, http://fengchj.com/?p=2255

2, http://blog.csdn.net/mycwq/article/details/26613275

那么, 从现有的资料,可以看出,正因为在Erlang 虚拟机内部,每个进程(process)都有自己的PCB,自己的stack和自己的私有heap(注意,现在的Erlang还不支持shared heap). Erlang的 GC 并不是"stop whe whole world",只是针对于每一个进程而言的.

Each process’ heap is garbage collected independently. Thus when one scheduler is collecting garbage for a process, other schedulers can keep executing other processes.

 

但是,并不是所有的数据都是private heap 的, 同样也有一些数据, 是存放在shared 区域的.

In addition, binaries larger than 64 bytes are stored in a common heap shared by all processes. ETS tables are also stored in a common heap.

binaries larger than 64 bytes, 也就是大家常说到的refc binaries, 这部分的解读网上也有很多, 举个栗子:

1, http://blog.csdn.net/zhongruixian/article/details/9450361

这个时候, Erlang的GC,就有可能遇到一些问题了,因为Erlang 虚拟机对待这部分shared memory 的GC,是采取引用计数器的.

 

然后,看一段完整的小程序:

 1 -module(refc_binary_test).
 2 
 3 -export ([start/0,
 4           handle_big_binary/1]).
 5 
 6 start() ->
 7     Me = erlang:self(),
 8     erlang:spawn(?MODULE, handle_big_binary, [Me]),
 9     receive
10         {ok, C} ->
11             io:format("----- get_bin_address C : ~p~n", [test:get_bin_address(C)]),
12             io:format("------- handled ~p~n", [erts_debug:get_internal_state({binary_info, C})]),
13             timer:sleep(1000000),
14             C;
15         _ ->
16             error
17     after 10000 ->
18             error
19     end.
20 
21 handle_big_binary(Me) ->
22     A = binary:copy(<<1>>, 1024*1024),
23     io:format("----- get_bin_address A : ~p~n", [test:get_bin_address(A)]),
24     io:format("------- resource ~p~n", [erts_debug:get_internal_state({binary_info, A})]),
25     <<B:1/binary, _/binary>> = A,
26     io:format("----- get_bin_address B : ~p~n", [test:get_bin_address(B)]),
27     erlang:send(Me, {ok, B}).

熟悉Ejabberd 的人,对这一模式应该不会陌生, handle_big_binary/1 的execute 进程可以映射到ejabberd 中的ejabberd_receiver module, start/0 可以看做是ejabberd 中的c2s 进程. ok, 和TCP socket 直接关联的进程会解析socket 数据, 解析完成后, 交给实际处理进程.

3> refc_binary_test:start().
----- get_bin_address A : "bin: size=1048576, ptr=0x18fc0040"
------- resource {refc_binary,1048576,{binary,1048576},0}
----- get_bin_address B : "bin: size=1, ptr=0x18fc0040"
----- get_bin_address C : "bin: size=1, ptr=0x18fc0040"
------- handled {refc_binary,1,{binary,1048576},0}

那么, 这个时候, 可以看到 变量 C 还占据这'{binary,1048576}' 的数据(1048576 是binary 的orig_size),即便是handle_big_binary 的进程在send/2 之后就已经结束生命. 然后, 可以放大一下这个小问题:

4> [proc_lib:spawn(refc_binary_test, start, []) || _ <- lists:seq(1, 1000)].

然后就会看到, beam.smp 进程已经占用了超过了1G的内存:(.

 

那,how fix ?

在binary module 中,referenced_byte_size/1 func:

If a binary references a larger binary (often described as being a sub-binary), it can be useful to get the size of the actual referenced binary. This function can be used in a program to trigger the use of copy/1. By copying a binary, one might dereference the original, possibly large, binary which a smaller binary is a reference to.

1 store(Binary, GBSet) ->
2   NewBin =
3       case binary:referenced_byte_size(Binary) of
4           Large when Large > 2 * byte_size(Binary) ->
5              binary:copy(Binary);
6           _ ->
7              Binary
8       end,
9   gb_sets:insert(NewBin,GBSet).

然后, 在看binary:copy/1 的function 描述:

This function will always create a new binary, even if N = 1. By using copy/1 on a binary referencing a larger binary, one might free up the larger binary for garbage collection.

这个时候再去refc_binary_test:start(). 就不会出现上面的问题了.

 

总结:

1, refc binary 的存储使用的是shared memory;

2, 对于refc binary 的GC 策略是引用计数器;

3, refc binary 的内存分配是连续的.

posted on 2015-02-24 21:03  _00  阅读(3614)  评论(0编辑  收藏