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

aikko · 2013年08月30日 · 最后由 douxiance 回复于 2015年08月20日 · 9457 次阅读
本帖已被设为精华帖!

最近我的一个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是不是只是进程内部把一使用的内存回收以便下次再次分配时使用,但是没有释放给系统?求大神指点。

共收到 29 条回复

泄漏了吧

#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的情况,结果是一样的。

13楼 已删除

#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值自动重启。

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

21楼 已删除

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一下

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