您的位置:首页 > 其它

erlang 内存被大量占用,跟踪过程

2012-03-26 13:39 190 查看
第一步:

查看进程数目是否正常? erlang:system_info(process_count).

第二步:

查看节点的内存消耗在什么地方?

> erlang:memory().

[{total,2099813400},

{processes,1985444264},

{processes_used,1985276128},

{system,114369136},

{atom,4479545},

{atom_used,4477777},

{binary,22756952},

{code,10486554},

{ets,47948808}]

显示内存大部分消耗在进程上,由此确定是进程占用了大量内存

第三步:

查看哪些进程占用内存最高?

> spawn(fun() -> etop:start([{output, text}, {interval, 1}, {lines, 20}, {sort, memory}]) end).

(以输出text方式启动etop,其间隔为1秒,输出行数为20行,按照内存排序. 这里spawn一个新进程,目的是输出etop数据时不影响erlang shell 输入.)

第四步:

查看占用内存最高的进程状态

> erlang:process_info(pid(0,12571,0)).

[{current_function,{mod_player,send_msg,2}},

{initial_call,{erlang,apply,2}},

{status,waiting},

{message_queue_len,0},

{messages,[]},

{links,[<0.12570.0>]},

{dictionary,[]},

{trap_exit,false},

{error_handler,error_handler},

{priority,normal},

{group_leader,<0.46.0>},

{total_heap_size,12538050},

{heap_size,12538050},

{stack_size,10122096},

{reductions,3795950},

{garbage_collection,[{min_bin_vheap_size,46368},

{min_heap_size,233},

{fullsweep_after,65535},

{minor_gcs,0}]},

{suspending,[]}]

其中” {total_heap_size,12538050},” 表示占用内存为 12358050 words(32位系统word size为4,64位系统word size为8, 可以通过erlang:system_info(wordsize) 查看),在64位系统下将近100M, 太夸张了!

第五步:

手动gc回收,希望问题可以解决

> erlang:garbage_collect(pid(0,12571,0)).

true

再次查看进程内存,发现没有任何变化!gc没有回收到任何资源,因此消耗的内存还在发挥作用,没有回收!

第六步:

不要怀疑系统,首先要怀疑自己的代码

认真观察代码,其大致结构如下:

send_msg(Socket, Pid) ->

try

receive

{send, Bin} ->

...

{inet_reply, _Sock, Result} ->

...

catch

_:_ ->

send_msg(Sock, Pid)

end.

其目的是循环等待数据,然后进行发送,其使用了try...catch捕获异常.

这段代码有问题么?

对,这段代码的确有问题, 其不是尾递归! try...catch会在stack中保存相应的信息,异常捕获需要放置在函数内部,所以send_msg最后调用的是try...catch,而不是自身,所以不是尾递归!

可以通过代码得到验证:

cat test.erl

-module(test).

-compile([export_all]).

t1() ->

Pid = spawn(fun() -> do_t1() end),

send_msg(Pid, 100000).

t2() ->

Pid = spawn(fun() -> do_t2() end),

send_msg(Pid, 100000).

send_msg(_Pid, 0) ->

ok;

send_msg(Pid, N) ->

Pid ! <<2:(N)>>,

timer:sleep(200),

send_msg(Pid, N-1).

do_t1() ->

erlang:garbage_collect(self()),

Result = erlang:process_info(self(), [memory, garbage_collection]),

io:format("~w ~n", [Result]),

io:format("backtrace:~w~n~n", [erlang:process_display(self(), backtrace)]),

try

receive

_ ->

do_t1()

end

catch

_:_ ->

do_t1()

end.

do_t2() ->

erlang:garbage_collect(self()),

Result = erlang:process_info(self(), [memory, garbage_collection]),

io:format("~w ~n", [Result]),

io:format("backtrace:~w~n~n", [erlang:process_display(self(), backtrace)]),

receive

_ ->

do_t2()

end.

版本1:erlc test.erl && erl -eval "test:t1()"

版本2:erlc test.erl && erl -eval "test:t2()"

你会看到版本1代码的调用堆栈在不断增长,内存也在增长, 而版本2函数调用地址保持不变,内存也没有发生变化!

总结:

1,服务器编程中,循环一定确保为尾递归

2,善于使用OTP,如果使用gen_server替换手写loop,就不会出现这个问题!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: