Erlang服务器内存耗尽bug跟踪过程本文描述朋友Erlang服务器内存耗尽bug的解决过程。首先说明一下问题,服务
Erlang服务器内存耗尽bug跟踪过程
本文描述朋友Erlang服务器内存耗尽bug的解决过程。
首先说明一下问题,服务器1千多人在线,16G内存快被吃光。玩家进程占用内存偏高:

接下来是解决过程。
第一步:
查看进程数目是否正常? erlang:system_info(process_count). 进程数目合理
第二步:
查看节点的内存消耗在什么地方?
> erlang:memory().
[{total,2099813400},
etop输出有点乱,超过一定范围变成了**,不过我们已经找到了内存占用最高的进程.
第四步:
查看占用内存最高的进程状态
> 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)).
send_msg(Socket, Pid) -> try receive {send, Bin} -> ... {inet_reply, _Sock, Result} -> ... catch _:_ -> [color=red]send_msg(Sock, Pid)[/color] end.
有问题的非尾递归调用存在于try/catch间receive中被隐去的部分里,而catch/end之间的那个 send_msg/2 调用则确实是尾递归,不会造成问题。同理,后面测试用例中造成内存占用不断上涨的问题在于 do_t1/0 中 try/catch 间 receive 中的 do_t1/0 调用,而非 catch/end 间的 do_t1/0 调用。
对于有疑问的 Erlang 代码,从 erlc 产生的字节码文件其实可以看得更清楚,尾递归调用会使用 call_last 字节码,而普通调用则使用 call 字节码,一目了然。比如如下代码
-module(x).-compile(export_all).x() ->tryx()catch_:_ -> x()end.
生成字节码文件:
$ erlc +"'S'" x.erl$ cat x.S...{function, x, 0, 2}. {label,1}. {func_info,{atom,x},{atom,x},0}. {label,2}. {allocate_zero,1,0}. {'try',{y,0},{f,3}}. [color=red]{call,0,{f,2}}.[/color] {try_end,{y,0}}. {deallocate,1}. return. {label,3}. {try_case,{y,0}}. [color=green]{call_last,0,{f,2},1}.[/color]...
文章看了几遍了,觉得原始的问题还没有解决:
以下这段代码的问题出在哪里?
send_msg(Socket, Pid) -> try receive {send, Bin} -> ... {inet_reply, _Sock, Result} -> ... catch _:_ -> send_msg(Sock, Pid) end.
按chaoslawful的说法
send_msg(Socket, Pid) -> try receive ... ... end catch _:_ -> send_msg(Sock, Pid) end.
这种结构的代码是没问题的(只要recive ...end 中间没有再调用send_msg/2)
8 楼 yjl49 2012-03-14 顺便问个问题编译后的字节码咋查看?谢谢