共计 1981 个字符,预计需要花费 5 分钟才能阅读完成。
概述
要从一次线上的内存透露说起. 最终定位到在异步 send 办法中, monitor 了被调用过程, 而又没有开释, 导致透露. 违反直觉的是, 这个透露是双向的, A monitor B 后, A 会记录 monitors B, B 会记录 monitored_by A. 过程 A 退出后, 过程 B 不会开释 monitored_by A. 所以, 在应用 monitor 时, 留神在任何状况下, 都要 demonitor.
如何定位 monitor 的透露
monitor 占用的内存被统计为 system/process, 属于 std_alloc 分配器.
能够用 process 内存排序, 找出透露的过程.
:recon.proc_count(:memory, 10)
接着, 能够用 process_info, 查看可疑的过程. monitors 是 monitors 的过程, monitored_by 是被哪些过程 monitor.
iex(xxxx@xxxx.)51> {_, b} = :erlang.process_info(pid(0,3182,0), :monitors) {:monitors, [#PID<55192.4940.0>, #PID<55192.4940.0>, #PID<55192.4940.0>, #PID<55192.4940.0>, #PID<55192.4940.0>, #PID<55192.4940.0>, #PID<55192.4940.0>, #PID<55192.4940.0>, #PID<55192.4940.0>, #PID<55192.4940.0>, #PID<55192.4940.0>, #PID<55192.4940.0>,
此外, 还能够用 allocated_types, :erlang.memory, 看到 system, std_alloc 有大量透露.
iex(xxxx@xxxx.)35> :recon_alloc.memory(:allocated_types) [binary_alloc: 7634944, driver_alloc: 6324224, eheap_alloc: 58241024, ets_alloc: 10780672, fix_alloc: 4751360, ll_alloc: 96993280, sl_alloc: 557056, std_alloc: 830242816, temp_alloc: 2228224]
iex(xxxx@xxxx.)4> :erlang.memory [total: 28235126584, processes: 441032432, processes_used: 440339304, system: 27794094152, atom: 1982841, atom_used: 1971208, binary: 15334152, code: 46042580, ets: 28443288]
如果狐疑 monitor 有透露, 能够间接依据 monitors/monitored_by 列表长度排序:
Enum.map(:erlang.processes(), fn proc -> {:erlang.process_info(proc, :monitors), proc} end) |> Enum.filter(fn v -> elem(v, 0) != :undefined end) |> Enum.map(fn v -> {length(elem(elem(v, 0), 1)), elem(v, 1)} end) |> Enum.sort(fn({a, _}, {b, _}) -> a > b end) |> Enum.take(100)
Enum.map(:erlang.processes(), fn proc -> {:erlang.process_info(proc, :monitored_by), proc} end) |> Enum.filter(fn v -> elem(v, 0) != :undefined end) |> Enum.map(fn v -> {length(elem(elem(v, 0), 1)), elem(v, 1)} end) |> Enum.sort(fn({a, _}, {b, _}) -> a > b end) |> Enum.take(100)
一些思考
为什么 monitor 是双向的:
显然 A monitor B, 单方都要记录, 否则
- A 不晓得 monitor 了 B
- B 退出时无奈告诉 A
为什么过程 A 退出时, 不告诉所有被 monitor 的过程
过程 A 退出时, 必然要告诉所有被 monitor 的过程开释这块内存. 集体认为在语言层面实现会更好. erlang 没做这点, 只有程序员确保肯定 demonitor. 就没有问题.
在节点链接断开时, monitored_by 的内存是会被开释的. 如果 erlang 连这点都没能保障, 无论如何都无奈实现正确了.
参考
https://erlang.org/doc/man/er…
https://ferd.github.io/recon/…