Ruby: 无法分配内存

Ruby: Cannot allocate memory

我正在 Rails 应用程序上开发 Ruby。我是 Ruby/Rails 的新手。 我使用 Ruby 2.2.0 和 Rails 4.2。当我 运行 这样的命令时:

rails g migration SomeMigrationName

它失败了

Cannot allocate memory - fork(2) (Errno::ENOMEM)

我在 2014 年年中使用 Macbook Pro,搭载 OS X 10.10,Vagrant/Virtualbox 到 运行 Rails 的虚拟机 (Ubuntu 14.04)发展。

这是我的 Vagrant 文件:

Vagrant.configure(2) do |config|
  config.vm.box = "ubuntu/trusty64"
  config.vm.network "forwarded_port", guest: 3000, host: 3000
  config.vm.synced_folder "dev", "/home/vagrant/dev"
  config.vm.synced_folder "opt", "/opt"
  config.vm.provider "virtualbox" do |vb|
    vb.memory = "512"
  end
end

我读到当 RAM 超出限制时会发生这样的错误,但我对另一个 运行 几个 Python/Tornado 应用程序的开发环境使用相同的配置(Vagrant 文件), MongoDB 和 Redis,一切正常。

我需要增加 vb.memory 值还是 Ruby 错误?

当 Ruby 调用 fork 时 OS 将复制整个父进程地址 space,即使 fork 仅被调用到 [=18] =] 另一个像 ls 这样的小过程。暂时,您的系统需要能够分配至少与 Ruby 父进程大小相当的内存块,然后才能将其折叠到子进程实际需要的大小。

所以 rails 通常非常耗费内存。那么如果某些东西使用 fork,你需要两倍的内存。

TL;DR 如果您可以控制代码,请使用 posix-spawn 而不是 fork。否则给你的 VM 1024MB 或一些额外的交换空间 space 来弥补 fork 调用

的空闲时间


示例 Ruby 内存使用情况 fork

使用一个随机的 VM,这个已经禁用交换 space:

$ free -m
             total       used       free     shared    buffers     cached
Mem:          1009        571        438          0          1         35
-/+ buffers/cache:        534        475
Swap:            0          0          0

查看 Mem: 行和 free 列。这大约是新进程的大小限制,在我的例子中是 438MiB

我的 buffers/cached 已经 flushed 参加了这次测试,所以我的 free 内存已达到极限。如果 buffers/cache 值很大,您可能需要考虑它们。 Linux 有能力在进程需要内存时清除过时的缓存。


用完一些内存

创建一个 ruby 进程,其中的字符串大小与您的可用内存大小相当。 ruby 过程有一些开销,因此它不会完全匹配 free.

$ ruby -e 'mb = 380; a="z"*mb*2**20; puts "=)"'
=)


然后把字符串稍微大一点:

$ ruby -e 'mb = 385; a="z"*mb*2**20; puts "=)"'
-e:1:in `*': failed to allocate memory (NoMemoryError)
        from -e:1:in `<main>'


在ruby进程中添加一个fork,减少mb直到它运行。

$ ruby -e 'mb = 195; a="z"*mb*2**20; fork; puts "=)"'
=)


稍大的fork进程会产生ENOMEM错误:

$ ruby -e 'mb = 200; a="z"*mb*2**20; fork; puts "=)"'
-e:1:in `fork': Cannot allocate memory - fork(2) (Errno::ENOMEM)
        from -e:1:in `<main>'


运行 带有反引号的命令启动带有 fork 的进程,因此具有相同的结果:

$ ruby -e 'mb = 200; a="z"*mb*2**20; `ls`'
-e:1:in ``': Cannot allocate memory - ls (Errno::ENOMEM)
        from -e:1:in `<main>'


所以你开始了,你需要大约两倍于系统上可用的父进程内存来派生一个新进程。 MRI Ruby 的多进程模型在很大程度上依赖于 fork,这是由于 Ruby 的设计使用了一次只允许一个线程执行的 global interpreter lock (GIL)每个 ruby 进程。

我相信 Python 在内部很少使用 fork。当您在 Python 中使用 os.fork 时,同样会发生:

python -c 'a="c"*420*2**20;'
python -c 'import os; a="c"*200*2**20; os.fork()'


Oracle 有一个 detailed article on the problem 并讨论使用 posix_spawn() 的替代方法。这篇文章针对 Solaris,但这是一个普遍的 POSIX Unix 问题,因此适用于 Linux(如果不是大多数 Unix)。

还有 posix-spawn 的 Ruby 实现,如果您可以控制代码,则可以使用它。此模块不会替换 Rails 中的任何内容,因此除非您自己替换对 fork 的调用,否则它不会在这里为您提供帮助。