memory-management - 解决大型二进制文件泄漏

标签 memory-management garbage-collection erlang elixir

我有一个 elixir/OTP 应用程序由于内存不足问题而在生产中崩溃。导致崩溃的函数每 6 小时在一个专用进程中调用一次。运行需要几分钟 (~30),如下所示:

def entry_point do
  get_jobs_to_scrape()
  |> Task.async_stream(&scrape/1)
  |> Stream.map(&persist/1)
  |> Stream.run()
end

在我的本地机器上,当函数运行时,我看到大型二进制文件内存消耗不断增长:

observer memory usage shows constant memory growth of large binaries

请注意,当我在运行该函数的进程上手动触发垃圾收集时,内存消耗会显着下降,因此对于几个不同的进程无法进行 GC 肯定不是问题,但只有一个进程无法正确 GC。此外,重要的是要说每隔几分钟进程 设法GC,但有时还不够。生产服务器只有 1GB RAM,并且在 GC 启动之前就崩溃了。

试图解决我遇到的问题Erlang in Anger (见第 66-67 页)。一个建议是将所有大型二进制文件操作放在一次性过程中。 scrape 的返回值函数是一个包含大型二进制文件的映射。因此,它们在 Task.async_stream 之间共享。 “ worker ”和运行该功能的进程。所以,理论上,我可以把 persist连同 scrape里面Task.async_stream .我宁愿不这样做,并继续调用persist。通过过程同步。

另一个建议是调用:erlang.garbage_collect定期。看起来它解决了问题,但感觉太hacky了。作者也不建议这样做。这是我目前的解决方案:

def entry_point do
  my_pid = self()
  Task.async(fn -> periodically_gc(my_pid) end)
  # The rest of the function as before...
end

defp periodically_gc(pid) do
  Process.sleep(30_000)
  if Process.alive?(pid) do
    :erlang.garbage_collect(pid)
    periodically_gc(pid)
  end
end

以及由此产生的内存负载:

observer memory usage after GC hack

我不太明白书中的其他建议如何解决这个问题。

在这种情况下你会推荐什么?保留hacky解决方案或有更好的选择。

最佳答案

erlang 虚拟机具有垃圾收集机制,默认情况下,该机制针对短期数据进行了优化。一个短暂的进程在死亡之前可能根本不会被垃圾收集,并且大多数垃圾收集运行只检查新添加的项目。在 GC 运行中幸存下来的项目将不会被再次检查,直到完成完整的扫描。

我建议您尝试调整 fullsweep_after 标志。可以通过 :erlang.system_flag(:fullsweep_after, value) 全局设置或使用 :erlang.spawn_opt/4 用于您的特定过程.

从文档:

The Erlang runtime system uses a generational garbage collection scheme, using an "old heap" for data that has survived at least one garbage collection. When there is no more room on the old heap, a fullsweep garbage collection is done.

Option fullsweep_after makes it possible to specify the maximum number of generational collections before forcing a fullsweep, even if there is room on the old heap. Setting the number to zero disables the general collection algorithm, that is, all live data is copied at every garbage collection.

A few cases when it can be useful to change fullsweep_after:

  • If binaries that are no longer used are to be thrown away as soon as possible. (Set Number to zero.)
  • A process that mostly have short-lived data is fullsweeped seldom or never, that is, the old heap contains mostly garbage. To ensure a fullsweep occasionally, set Number to a suitable value, such as 10 or 20.
  • In embedded systems with a limited amount of RAM and no virtual memory, you might want to preserve memory by setting Number to zero. (The value can be set globally, see erlang:system_flag/2.)


默认值为 65535(除非您已经通过环境变量 ERL_FULLSWEEP_AFTER 对其进行了更改),因此任何较低的值都会使垃圾收集更加激进。

这是一个很好的阅读主题:https://www.erlang-solutions.com/blog/erlang-19-0-garbage-collector.html

关于memory-management - 解决大型二进制文件泄漏,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/43613027/

相关文章:

memory-management - 在 Linux 中模拟设备 - 需要一种在 RAM 中分配资源的方法

python - 是否可以从 Python 获取 "high water mark"的内存使用情况?

windows - 内核内存地址空间

java - 自动删除 WeakHashMap 中的条目

erlang - 如何为RabbitMQ/Erlang使用多个配置文件

C# - 传递 3 个参数与传递具有 30 个属性的对象 -

java - Java Flight Recorder 结果中的垃圾收集统计信息

python - Python GC 也会关闭文件吗?

design-patterns - 在 Erlang 中实现取消

concurrency - Erlang 中的 PID 列表