binary类别

根据存储位置不同:

  • Refc binaries (short for reference-counted binaries, stored on share heap)
  • Heap binaries(stored on the process heap,small binaries, up to 64 bytes)

根据引用binary数据方式不同,区分两种:

  • sub binaries(A sub binary is a reference into a part of another binary)
  • match contexts(similar to a sub binary, it contains a direct pointer to the binary data)

构造binary优化机制

对于使用append方式构造新binary时候,并不是每次都重新复制申请新内存构造,会根据上一个binary优化,直接在原来预先申请好的内存空间添加新内存。
但如果对这个binary进行传输,match之类的操作,又会把原来申请好的内存优化掉(shrink)。

1
2
3
4
5
6
Bin0 = <<0>>,                    %% 1
Bin1 = <<Bin0/binary,1,2,3>>, %% 2
Bin2 = <<Bin1/binary,4,5,6>>, %% 3
Bin3 = <<Bin2/binary,7,8,9>>, %% 4
Bin4 = <<Bin1/binary,17>>, %% 5 !!!
{Bin4,Bin3} %% 6

2,5会copy原binary数据创建新的binary,新binary内存=max(size(Bin0), 256),预留一定空间用于append
3,4会在上一个binary后面直接添加数据

1
2
3
Bin1 = <<Bin0,...>>,
PortOrPid ! Bin1, 或 <<X,Y,Z,T/binary>> = Bin1, 或发送到其他port
Bin = <<Bin1,...>> %% Bin1 will be COPIED,前面操作原binary会shrink,导致这里发生copy

垃圾回收

由于erlang的gc使用的是分代gc,一般情况下一次小型 GC(minor_gcs) 会把需要的数据移到老堆中。然后,回收其余的数据,最后也可能会重新分配一些空间。经过一定数量的小型 GC 和重新分配,会执行一次全面的 GC,这次 GC 会详细检查老堆和新堆,释放更多的空间,可以定时进行手动的gc和设置进程的{fullsweep_after, xx} ,让进程更多Fullsweep,但同样也要牺牲更多cpu。

某个进程的gc设置和当前小型gc次数可以通过 process_info(PID, garbage_collection)查看。

一般情况小型gc(minor)和深层次FullSweep发生情况:

  1. 堆耗尽触发minor,仍然无法满足触发FullSweep
  2. N次minor后触发FullSweep
  3. 内存快速申请不够用直接FullSweep
  4. 手动gc触发FullSweep

对于binary,大于 64 字节的binary会分配在一个binary专用的全局堆上,每个使用它的进程都会在自己的局部堆中持有一个对其的本地引用。这类binary是引用计数的,仅当所有进程中所持有的引用都被垃圾回收了,才会释放binary的内存。

日常编程中如果一个binary在多个进程传递,或者长久持有一个sub binaries或match contexts,是有可能导致bianry不能被正确回收。

如检查到内存binary增大, 可使用recon:bin_leak(Max)可观察gc前后的变化情况,这个调用会显示出每个进程所持有的以及随后释放的 binary 数量的差值。如果发现执行这个调用后,VM 使用的内存急剧降低,则说明 VM 持有了大量没用的 refc binary,需要重点关注该进程。

如果非个别进程占特别高,则观察是不是有大量同类进程持有了数量巨大的refc binary。

查到可疑的进程后就检查代码引用binary方式是否正确,gc策略是否合理,另外也需要避免binary在一些进程仅仅只是传递作用。

参考:
Binary Constructing and Matching Binaries
Erlang Garbage Collection Details and Why It Matters