乐趣区

elixirerlang内存泄漏排查

前言

对服务端程序来说, 内存泄漏是经常会面临的问题. 使用 erlang 的情况下, 不用程序员手动管理内存. 如果不写 c driver, 一般的内存问题还是很容易定位的. 这篇 blog 对常见的内存泄漏类型, 排查手段做个小结.

内存泄漏类型

process 泄漏

如果没有 etop

iex(xxxx@xxxx.)1> :erlang.system_info(:process_count)
5369

可以通过 process_count 来获取 erlang vm 中已分配的 process 数量. 若 process 数量和业务实际需要不吻合, 则需要排查.

消息堆积

iex(xxxx@xxxx.)5> spawn fn -> :etop.start([sort: :msg_q]) end 
#PID<0.6255.1>
                                              
========================================================================================
 'xxxx@xxxx.'                                  03:04:46
 Load:  cpu         0               Memory:  total      147234    binary       2839
        procs    5371                        processes   59008    code        42641
        runq        0                        atom         1722    ets          8239
                                              
Pid            Name or Initial Func    Time    Reds  Memory    MsgQ Current Function
----------------------------------------------------------------------------------------
<7796.0.0>     init                     '-'  339058   29540       0 init:loop/1         
<7796.1.0>     erts_code_purger         '-'  479850  285160       0 erts_code_purger:wai
<7796.2.0>     erts_literal_area_co     '-'  337591    2688       0 erts_literal_area_co
<7796.3.0>     erts_dirty_process_s     '-'   37924    2688       0 erts_dirty_process_s

一般消息堆积都会伴随着 memory 增长, 不管是 sort by msg_q 或 memory, 都很容易发现问题.
如果没有 etop

iex(xxxx@xxxx.)8> Enum.map(:erlang.processes(), fn proc -> {:erlang.process_info(proc, :message_queue_len), proc} end) |> Enum.sort(fn({{_, a}, _}, {{_, b}, _}) -> a > b end) |> List.first
{{:message_queue_len, 0}, #PID<0.32638.0>}

ets 表泄漏

找出占用最多内存的 ets 表

iex(7)> :ets.all() |> Enum.map(fn ets_name -> {:ets.info(ets_name, :memory), ets_name} end) |> Enum.sort(fn a, b -> a > b end)
[{18002942, :test},
  {41940, #Reference<0.3983585142.1897791489.87703>},
...
]

整体内存分析

:erlang.memory 可以一眼看出是否 ets 表存在泄漏
值得注意的是, 大于 64bit 的 binary, 会在:erlang.memory 的 binary 项体现. 不会计入 ets 项中.

65bit
iex(1)> :ets.new(:test, [:public, :named_table])
:test
iex(2)> :erlang.memory
[
  total: 23688632,
  processes: 4940400,
  processes_used: 4939456,
  system: 18748232,
  atom: 463465,
  atom_used: 442288,
  binary: 27872,
  code: 8462310,
  ets: 589664
]
iex(3)> for num <- 1..1000000 do
...(3)> :ets.insert(:test, {num, :crypto.strong_rand_bytes(65)})
...(3)> end
[true, true, true, true, true, true, true, true, true, true, true, true, true,
 true, true, true, true, true, true, true, true, true, true, true, true, true,
 true, true, true, true, true, true, true, true, true, true, true, true, true,
 true, true, true, true, true, true, true, true, true, true, true, ...]
iex(4)> :erlang.memory
[
  total: 284511736,
  processes: 33626760,
  processes_used: 33625816,
  system: 250884976,
  atom: 463465,
  atom_used: 446381,
  binary: 112090520,
  code: 8553627,
  ets: 120619384
]
64bit
iex(1)> :ets.new(:test, [:public, :named_table])
:test
iex(2)> :erlang.memory
[
  total: 23569856,
  processes: 4778728,
  processes_used: 4777784,
  system: 18791128,
  atom: 463465,
  atom_used: 442288,
  binary: 70736,
  code: 8462310,
  ets: 589680
]
iex(3)> for num <- 1..1000000 do                                
...(3)> :ets.insert(:test, {num, :crypto.strong_rand_bytes(64)})
...(3)> end
[true, true, true, true, true, true, true, true, true, true, true, true, true,
 true, true, true, true, true, true, true, true, true, true, true, true, true,
 true, true, true, true, true, true, true, true, true, true, true, true, true,
 true, true, true, true, true, true, true, true, true, true, true, ...]
iex(4)> :erlang.memory
[
  total: 204325192,
  processes: 33373520,
  processes_used: 33372576,
  system: 170951672,
  atom: 463465,
  atom_used: 447944,
  binary: 39168,
  code: 8653586,
  ets: 152623976
]

数据过大

首先, 应该能估算出业务大致的内存占用. 可以通过 process_info, 找出可疑的进程.

iex(11)> :erlang.process_info(:ets.info(:test, :owner), :memory)
{:memory, 28693220}

通过:sys.get_state 可以发现一些逻辑错误造成的, list/map 无限增长的 bug.

iex(xxxxx@xxxxx.)13> :sys.get_state(:erlang.list_to_pid('<0.2362.0>'))
{:state, {:local, :prometheus_sup}, :one_for_one, {[], %{}}, :undefined, 5, 1,
 [], 0, :prometheus_sup, []}

内存估算

https://github.com/okeuday/erlang_term
http://erlang.org/doc/efficiency_guide/advanced.html#id68923

退出移动版