Ruby:无法分配内存

Nod*_*iya 11 ruby ruby-on-rails vagrant

我正在开发Ruby on Rails应用程序.我是Ruby/Rails的新手.我使用Ruby 2.2.0和Rails 4.2.当我运行如下命令时:

rails g migration SomeMigrationName
Run Code Online (Sandbox Code Playgroud)

它失败了

Cannot allocate memory - fork(2) (Errno::ENOMEM)
Run Code Online (Sandbox Code Playgroud)

我在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
Run Code Online (Sandbox Code Playgroud)

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

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

Mat*_*att 27

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

所以rails通常非常耗费内存.然后,如果有东西使用fork,你需要两倍的内存.

TL; DR如果您控制代码,请使用posix-spawn而不是fork.否则,请为您的VM提供1024MB或一些额外的交换空间,以便为fork通话留出空间


示例Ruby内存使用情况fork

拿一个随机虚拟机,这个虚拟机已禁用交换空间:

$ free -m
             total       used       free     shared    buffers     cached
Mem:          1009        571        438          0          1         35
-/+ buffers/cache:        534        475
Swap:            0          0          0
Run Code Online (Sandbox Code Playgroud)

查看Mem:行和free列.这大概是关于新流程的大小限制,在我的情况下是438MiB

buffers/cached已经为这次测试冲了过来,以便我的free记忆达到极限.如果buffers/cache值很大,您可能需要考虑这些值.当进程需要内存时,Linux能够驱逐过时的缓存.


用完一些记忆

使用大小与可用内存大小相关的字符串创建ruby进程.这个ruby过程有一些开销,所以它不会完全匹配free.

$ ruby -e 'mb = 380; a="z"*mb*2**20; puts "=)"'
=)
Run Code Online (Sandbox Code Playgroud)


然后使字符串稍大:

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


添加fork到ruby进程,减少mb直到它运行.

$ ruby -e 'mb = 195; a="z"*mb*2**20; fork; puts "=)"'
=)
Run Code Online (Sandbox Code Playgroud)


稍大的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>'
Run Code Online (Sandbox Code Playgroud)


使用反引号运行命令会启动该进程,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>'
Run Code Online (Sandbox Code Playgroud)


所以,你需要大约两倍于系统上可用的父进程内存来分叉一个新进程.MRI Ruby在很大程度上依赖于fork它的多进程模型,这是由于Ruby的设计使用了全局解释器锁(GIL),它只允许每个ruby进程一次执行一个线程.

我相信Python在fork内部使用的次数要少得多.当你os.fork在Python中使用时,会发生同样的情况:

python -c 'a="c"*420*2**20;'
python -c 'import os; a="c"*200*2**20; os.fork()'
Run Code Online (Sandbox Code Playgroud)


Oracle有关于该问题详细文章,并讨论使用替代方案posix_spawn().本文针对Solaris,但这是一个普遍的POSIX Unix问题,因此适用于Linux(如果不是大多数Unices).

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