Ruby Ruby 的 GC 不释放内存给回系统的?

aikko · 2013年08月30日 · 最后由 douxiance 回复于 2015年08月20日 · 11160 次阅读
本帖已被管理员设置为精华贴

最近我的一个 ruby on rails 的项目,部署采用的是(nginx+passenger),由于访问量大概增了 6 倍左右,出现了一个之前没有注意到的问题,就是每个 ruby 进程的内存一直不断地增长,有一次跑了 3 天没管,多个 ruby 进程从刚开始的 100 多 M 到达 500M 左右,不得已每天晚上 5 点左右重启一下服务以便释放内存。换了 ruby 2.0 有所改善,但是还是不明显。

跑了 11 几个小时左右

跑了 80 几个小时左右

在网上看到了Robbin 的这篇文章后,在生产环境选了一台机器观察下,我发现从来没有内存使用有降低的情况。于是开始猜测是不是没有触发 GC,我的 ruby gc 参数参考 37singals 和 twitter 的 配置如下:

export RUBY_HEAP_MIN_SLOTS=1250000
export RUBY_HEAP_SLOTS_INCREMENT=100000
export RUBY_HEAP_SLOTS_GROWTH_FACTOR=1
export RUBY_GC_MALLOC_LIMIT=30000000
export RUBY_HEAP_FREE_MIN=12500

GC::Profiler 去观察 ruby 的 GC 情况,得到结果如下:

明显很多次自动触发了 GC,后面我自己强制调用 GC.start,Use Size 和 Total Size 都降低了很多,但是该 ruby 进程的内存占用量始终不降。(中间我观察有几次次非常小幅的下降 ---- 每次大概 1% 约 40m,但总体在增长)

当执行完 GC 后,如果在改 ruby 进程里面再次大量的分配对象时,整体内存增长不大,于是我猜测 ruby 所谓的 GC 是不是只是进程内部把一使用的内存回收以便下次再次分配时使用,但是没有释放给系统?求大神指点。

泄漏了吧

#1 楼 @huacnlee 我也想过是不是泄露了,不过后面的测试 GC 的时候使用的是下面的代码,应该不会有泄露吧

GC::Profiler.enable
GC::Profiler.clear

a = []
10_000_000.times do
  a << [rand(36**10).to_s(36)]
end

GC::Profiler.report

a = []

GC.start

GC::Profiler.report

Ruby China 的,一般都在 250M 以内

25729 那个 Ruby 进程和其他的有什么区别?

貌似有这个说法,但是一直没找到可以很好的解释的文章

25729 是我 rails consle 起来后 跑 2 楼那个测试代码,不断地释放 a 和 再分配 a 最终结果。 其他进程就是我用 passenger 起来的进程

然后 25729 就从 100 多的 m 涨到了 2G

  1. 确定你没有 GC.disable ?
  2. 确定你没有装 RMagick

#3 楼 @huacnlee 我这边目前跑了 20 多个小时的大概 300 多 M,上面那个 900m 我是参考的 VIRT,我改下,应该用 RES,从下图可以看出每个 ruby 进程的内存使用量和已经运行的时间基本正相关:

#7 楼 @huacnlee 1、确定 GC.enable 了 2、没有用 RMagick,但是用了 gem 'mini_magick', '3.5.0'

virt 不是真正使用的内存可以不管. res (resident) 准确一点.

会释放给系统的

可能是你的代码里有内存泄漏.

不过 lazy sweep 机制使得 gc 完以后, 分配内存时, 才去 sweep, 而且 sweep 到能分配的程度后, 就不再 sweep 了. 你可以试试 GC.stress = true (但会变慢很多, 弄完记得 GC.stress = false)

32 位机器的保守式 gc 扫栈也会导致一些泄漏, 不过 64 位机器几乎不会.

ruby gc 没有 compact 机制, 长时间运行内存碎片增多也会导致占用内存增多, 但不明显.

profiler 吃 2g 内存不奇怪啊, 你去掉 profiler 然后看看吃的内存...

#10 楼 @luikore 我使用的 64 位机器试了下 disable prifiler 的情况,结果是一样的。

14 楼 已删除

#13 楼 @luikore 因为 a 在 top level,被全局引用,不能被 GC?

#13 楼 @luikore 这 2 种方式结果一样,还是没有释放给系统。

@hooopo @aikko 呃我弄错了请忽略 #13 楼

中间产生大量临时对象的话是有可能出现这种情况的。GC 只是把没用的对象放回 free list.

ruby 是通过多个 heaps 来管理内存的, 每个 heap 有许多 slot 用来存放对象,只有当一个 heap 的 slot 全部是 free 的时候 ruby 才会把这个 heap 释放掉,把内存交还 os.

建议做 memory profile, 看看什么地方产生的对象很多。

#17 楼 @jan 听起来像 stl 的容器。

我的一个项目也是用 Nginx+Passenger,每个进程启动占用 100M,之后内存也会涨一些,但是不会释放。我采取的策略是,设置 PassengerMaxRequests 参数,在处理指定数量的请求后会自动重启。因为我用的是免费版,付费版还可以设置内存到 Max 值自动重启。

应该不会马上还给系统,留着给以后的新变量使用吧?

22 楼 已删除

Test Ruby's ability to release memory back to system. (1.9.3 vs 2.0) https://gist.github.com/rjackson/5071864

我以为 MRI Ruby runtime 本来就不会把用不到的记忆体释回给 OS:http://stackoverflow.com/questions/5968992/ruby-hash-memory-leak-after-key-deletion

#1 楼 @huacnlee 请问下,在写 rails app 的时候有哪些地方需要注意,才不会导致内存泄漏,我的一个 rails app 用了很多 array 和 hash,包括局部变量和实例变量,开始启动内存在 180 左右,然后就不断的上涨,passenger 启动的 4 个进程都涨到了 700 多,ruby-china 也是长时间运行,内存基本不上涨吗?

#24 楼 @zhenjunluo 我不知道你做了什么,所以没法帮助你

#25 楼 @huacnlee 可能我没描述清楚,因为你提到了内存泄漏,我想问的是怎样才能不泄漏内存,写程序的时候有哪些地方需要注意

php 的主要优势就是用这种非常驻内存的解析方式解决了内存泄漏

可以换成 unicorn 看看,在内存达到设定的的条件时,该 ruby 进程就会自杀,然后会重新启动一个新的 ruby 进程,这样解决了 ruby 内存不断上涨并且不释放内存的问题 http://ruby-china.org/topics/12033

Mark 一下

需要 登录 后方可回复, 如果你还没有账号请 注册新账号