锁定老帖子 主题:对Ruby VM的GC的思考
该帖已经被评为精华帖
|
|
---|---|
作者 | 正文 |
发表时间:2008-09-02
由于pmap命令可以dump进程的内存映射表,因此我们可以对比RubyVM和JVM在GC前后的内存映射情况。比方说Ruby的内存状况大概是这样的: 下图是一个Ruby进程的物理内存映射表,堆内存占据的空间是我抽取出来的三行: 00000000005d4000 125260K rwx-- [ anon ] 0000002a95c23000 23456K rw--- [ anon ] 0000002a99186000 50980K rw--- [ anon ] 0000000000400000 760K r-x-- /usr/local/ruby/bin/ruby 00000000005bd000 92K rw--- /usr/local/ruby/bin/ruby 00000000005d4000 125260K rwx-- [ anon ] 0000002a95556000 84K r-x-- /lib64/ld-2.3.3.so 0000002a9556b000 12K rw--- [ anon ] 0000002a9556e000 24K r--s- /usr/lib64/gconv/gconv-modules.cache 0000002a95574000 4K rw--- [ anon ] 0000002a95577000 12K rw--- [ anon ] 0000002a9557a000 204K r---- /usr/lib/locale/en_US.utf8/LC_CTYPE 0000002a9566a000 12K rw--- /lib64/ld-2.3.3.so 0000002a9566d000 8K r-x-- /lib64/libdl.so.2 0000002a9566f000 1024K ----- /lib64/libdl.so.2 0000002a9576f000 4K rw--- /lib64/libdl.so.2 ...... 可以看出来Ruby的堆内存分配比较连续,分段不多。而JVM的堆内存分配段就很多了,由于堆地址段太多,我就不贴出来了,大家可以自己观察。 由于Ruby的内存分配算法和回收算法还是比较原始的,因此在进行多次回收之后,内存堆很容易出现大量的内存碎片,很多内存碎片并不能够被有效的利用,并且ruby没有好的碎片归并压缩算法,因此碎片造成的内存堆地址空间浪费就会越来越大。其结果就是Ruby进程在长期高负载运行之下,表现出缓慢的内存泄漏现象! 对比JVM的堆分配,他分配了很多的段,每个段的内存存活期不一样,根据分代算法,可以把不同存活期的对象在堆之间移动,堆内部则进行碎片归并。比方说我对一个Tomcat应用服务器的实际应用先pmap记录内存映射,然后GC,再pmap记录内存映射,两者diff一下,就可以发现某些堆在收缩,但是某些堆甚至在GC后扩张了,这便是对象在堆之间进行移动的现象。因此我不得不赞赏一下JVM的内存分配。 话说回来,由于Ruby的VM内存分配的碎片问题,导致Ruby进程几乎无可避免的内存泄漏。其结果就是你必须实时监控Ruby进程的运行情况,一旦发现内存使用超过限额,则必须果断的杀掉进程重起。比方说现在很多流行的Rails网站都是用monit去监控mongrel实例,一旦发现内存使用超过限额就杀掉重起。这种监控方式虽然可以有效的解决ruby的内存泄漏问题,但是太过简单粗暴,如果杀掉进程重起的时候,Ruby进程正好在处理请求,那么该请求是肯定会失败掉的,对于一些极端的情况,似乎很难令人接受这种现象的存在。我现在没有用monit,而是自己写shell脚本来监控(写几行shell就可以搞定的事情没必要那么麻烦搞什么monit),每天大概能出现两三次这种需要杀掉重起的情况,对比每天要处理将近100 万动态请求来说,可靠性还是达到了99.999%,还算可以。 那么将于今年年底发布的ruby 1.9.1能够解决这个问题吗? 答案是悲观的,1.9的GC并没有本质的提高,可以预见还是会出现无可避免的内存泄漏问题。但是1.9的内存泄漏会比现在的1.8要轻微一些,原因是1.9会对堆空间的内存碎片从小到大进行排序,因此对于内存碎片的利用率要高一些,再加上1.9的GC相对来说更积极主动一些,因此在一定程度上可以减轻内存碎片问题。 但不管怎么说,在可以预见的未来,Ruby的内存泄漏问题无可避免,我们还是要做好动不动杀掉ruby进程重起的准备。所以你要好好操练一下monit,或者像我一样,写个shell脚本进行监控,用crontab每隔几分钟跑一下。 我们的Rails部署方式是lighttpd+fcgi,用Unix Socket通信,监控脚本示例如下: #!/bin/sh . /etc/profile.local RUBY_HEAP_MIN_SLOTS=600000 RUBY_HEAP_SLOTS_INCREMENT=600000 RUBY_HEAP_FREE_MIN=100000 RUBY_GC_MALLOC_LIMIT=60000000 RAILS_ENV=production export RUBY_HEAP_MIN_SLOTS RUBY_HEAP_SLOTS_INCREMENT RUBY_GC_MALLOC_LIMIT RUBY_HEAP_FREE_MIN RAILS_ENV SPAWN=/usr/local/lighttpd/bin/spawn-fcgi DISPATCH_PATH=/.../yourrailsapp/public/dispatch.fcgi SOCKET_PATH=/yourlighttpd/socket PID_PATH=/yourlighttpd/pids RSS_MAX=307200 for PSDATA in `ps -e v | grep dispatch.fcgi | awk '{print $1 ":" $8 }'` do RSS=${PSDATA#*:} PID=${PSDATA%:*} if [ $RSS -ge $RSS_MAX ]; then echo echo `date` echo "----------------------------------------" echo "PID["$PID"]: RSS="$RSS"KB is too big!" for num in 0 1 2 3 4 5 6 7 8 9 do if [ $PID -eq `cat $PID_PATH/javaeye.pid-$num` ]; then echo "PID["$PID"] using socket: "$num kill -9 $PID rm -rf $SOCKET_PATH/javaeye.socket-$num $SPAWN -f $DISPATCH_PATH -s $SOCKET_PATH/javaeye.socket-$num -P $PID_PATH/javaeye.pid-$num fi done fi done sleep 10 for num in 0 1 2 3 4 5 6 7 8 9 do if [ ! -d /proc/`cat $PID_PATH/javaeye.pid-$num` ]; then echo echo "Ruby Server using socket: "$num" had been crashed, need to be starting..." rm -rf $SOCKET_PATH/javaeye.socket-$num $SPAWN -f $DISPATCH_PATH -s $SOCKET_PATH/javaeye.socket-$num -P $PID_PATH/javaeye.pid-$num fi done 声明:ITeye文章版权属于作者,受法律保护。没有作者书面许可不得转载。
推荐链接
|
|
返回顶楼 | |
发表时间:2008-09-03
简单粗暴是针对问题最直接的解决办法。
|
|
返回顶楼 | |
发表时间:2008-09-03
RSS_MAX=307200
--这个值是哪里得来的?经验? |
|
返回顶楼 | |
发表时间:2008-09-03
mod_rails的作者也做了个Ruby Enterprise Edition (http://www.rubyenterpriseedition.com/ ),基本上就是改进了GC.用了一阵子了一直很稳定,但我们的流量都没有javaeye这么大.
我的一个做JVM的朋友按照JVM的实现也重写过ruby gc,效果也很好.但只能限于proof of concept,没有时间和经历去雕琢. |
|
返回顶楼 | |
发表时间:2008-09-03
yawl 写道 mod_rails的作者也做了个Ruby Enterprise Editio (http://www.rubyenterpriseedition.com/ ),基本上就是改进了GC.用了一阵子了一直很稳定,但我们的流量流量都没有javaeye这么大.
我的一个做JVM的朋友按照JVM的实现也重写过ruby gc,效果也很好.但只能限于proof of concept,没有时间和经历去雕琢. 找个时间试试Ruby Enterprise Editio,谢谢推荐。 |
|
返回顶楼 | |
发表时间:2008-09-03
yawl 写道 mod_rails的作者也做了个Ruby Enterprise Editio (http://www.rubyenterpriseedition.com/ ),基本上就是改进了GC.用了一阵子了一直很稳定,但我们的流量流量都没有javaeye这么大.
之前有关注过这个VM,看FAQ不能在64位系统上运行,而我们目前网站是运行在64位操作系统上。 引用 Ruby Enterprise Edition is a bit faster than standard Ruby, because of the improved memory allocator. However, this memory allocator does not work on 64-bit platforms. As a result, on 64-bit platforms, Ruby Enterprise Edition is slightly slower than standard Ruby (by a few percent). How much slower depends on the application and workload.
这个VM配合mod_rails比较合适用来做虚拟主机提供rails host服务 而Rails 2.2的线程安全也应该有这方面的考虑 |
|
返回顶楼 | |
发表时间:2008-09-03
phusion passenger (mod_rails) 的作者好像就做过改进 GC 的工作,也提交了一些补丁到 Ruby core 。不知道改进明显不明显。
|
|
返回顶楼 | |
发表时间:2008-09-05
嗯,你说的这个就是Enterprise Ruby(很雷的名字...他们也说要选个更适合的名字),Matz说“考虑”加入到core中,但是好像没下文了。
pluskid 写道 phusion passenger (mod_rails) 的作者好像就做过改进 GC 的工作,也提交了一些补丁到 Ruby core 。不知道改进明显不明显。
|
|
返回顶楼 | |
发表时间:2008-09-05
按mod_rails的作者来说
改进是很明显的 引用 This has huge performance implications. The copy-on-write friendly mark table makes the garbage collector about 0%-20% slower, depending on the application and the workload. However, the non-copy-on-write friendly mark table is enabled by default, so by default there is only a 1% performance penalty. This performance penalty comes from the fact that marking an object now requires a function call which sets the mark flag, instead of setting the mark flag directly. But I think 1% is acceptable.
但是,近来想把这个gc算法patch到ruby core搞定似乎是很困难了 引用 Unfortunately the discussion stranded. Matz had some concerns about performance, which is why I made the mark table implementation pluggable. I will re-submit the patch for further evaluation when the time is right.
|
|
返回顶楼 | |
发表时间:2008-09-09
ruby要进入企业级的应用还有很长的一段路要走。使用ROR开发的感觉确实畅快,但是现在不得不退回到java,虽然心有不甘,但是没有办法,先观望一段时间,希望ruby走好。
|
|
返回顶楼 | |