Ruby:无法分配内存

时间:2015-02-02 20:17:13

标签: ruby-on-rails ruby vagrant

我正在开发Ruby on Rails应用程序。我是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运行虚拟机(Ubuntu 14.04)进行Rails开发。

这是我的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超出限制时会发生这样的错误,但是我使用相同的配置(Vagrant文​​件)来运行几个Python / Tornado应用程序,MongoDB和Redis的另一个开发环境,它都可以工作细

我是否需要增加vb.memory值或者它是一个Ruby错误?

1 个答案:

答案 0 :(得分:25)

当Ruby调用fork时,操作系统将复制整个父进程地址空间,即使fork仅被调用exec另一个小进程,如ls。暂时,您的系统需要能够分配一块内存,至少是Ruby父进程的大小,然后再将其折叠到子进程实际需要的内容。

所以导轨通常非常耗费内存。然后,如果某些内容使用fork,则需要两倍的内存。

TL; DR 如果您控制代码,请使用posix-spawn代替fork。否则给你的VM 1024MB或一些额外的交换空间来弥补fork电话的闲暇


使用fork

的Ruby内存使用示例

使用随机虚拟机,此虚拟机已禁用交换空间:

$ 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列。这大约是您的新流程的大小限制,在我的情况下438 MiB

我的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(如果不是大多数Unices)。

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