`
robbin
  • 浏览: 4836362 次
  • 性别: Icon_minigender_1
  • 来自: 上海
博客专栏
377a9ecd-1ea1-34ac-9530-9daa53bb2a7b
robbin谈管理
浏览量:138034
社区版块
存档分类
最新评论

对Ruby VM的GC的思考

    博客分类:
  • Ruby
阅读更多
Ruby虽然是动态脚本语言,但是和Java一样,带有VM,有自己的内存堆,创建对象的时候在堆里面分配内存,对象使用完毕由GC进行回收。但是通过我们运营Rails网站两年多的实践来看,Ruby VM的GC还是存在很大的问题。简单的来说,就是GC之后,尽管对象已经完全回收,但是物理内存释放不够充分,有泄漏的现象。通过pmap来dump ruby进程物理内存地址映射表进行分析,观察到ruby的内存堆总是在不停的扩张,GC之后回收不干净。而我对比观察Java VM,其Full GC之后,物理内存释放的非常干净。所以用Ruby做服务器长期的跑,就会发现Ruby进程没有理由的缓慢泄漏内存,内存堆缓慢增长,貌似没有上限。

由于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 

分享到:
评论
11 楼 sevk 2012-01-19  
JAVA开源不,把JAVA的GC移植到RUBY里来。
linux就是好,可以像组装机一样自己选择GC。
java的thread也不错,也可以鉴戒,开源就是好。
10 楼 liangshixing 2008-11-23  
为什么不用JRuby呢?
9 楼 chengj 2008-09-09  
ruby要进入企业级的应用还有很长的一段路要走。使用ROR开发的感觉确实畅快,但是现在不得不退回到java,虽然心有不甘,但是没有办法,先观望一段时间,希望ruby走好。
8 楼 t0uch 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.


7 楼 leondu 2008-09-05  
嗯,你说的这个就是Enterprise Ruby(很雷的名字...他们也说要选个更适合的名字),Matz说“考虑”加入到core中,但是好像没下文了。

pluskid 写道
phusion passenger (mod_rails) 的作者好像就做过改进 GC 的工作,也提交了一些补丁到 Ruby core 。不知道改进明显不明显。

6 楼 pluskid 2008-09-03  
phusion passenger (mod_rails) 的作者好像就做过改进 GC 的工作,也提交了一些补丁到 Ruby core 。不知道改进明显不明显。
5 楼 QuakeWang 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的线程安全也应该有这方面的考虑
4 楼 robbin 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,谢谢推荐。
3 楼 yawl 2008-09-03  
mod_rails的作者也做了个Ruby Enterprise Edition (http://www.rubyenterpriseedition.com/ ),基本上就是改进了GC.用了一阵子了一直很稳定,但我们的流量都没有javaeye这么大.

我的一个做JVM的朋友按照JVM的实现也重写过ruby gc,效果也很好.但只能限于proof of concept,没有时间和经历去雕琢.
2 楼 iceskysl 2008-09-03  
RSS_MAX=307200 
--这个值是哪里得来的?经验?
1 楼 iceskysl 2008-09-03  
简单粗暴是针对问题最直接的解决办法。

相关推荐

    ruby2.6.1.zip

    1. **YARV虚拟机优化**:Ruby 2.6.1基于YARV(Yet Another Ruby VM)虚拟机,这个版本对YARV进行了优化,提高了整体性能,尤其是对于CPU密集型的任务。 2. **编译器优化**:Ruby 2.6引入了新的编译器框架,使得语法...

    ruby-2.0.0-p0.tar

    8. **YARV虚拟机优化**:Ruby 2.0继续使用YARV(Yet Another Ruby VM)作为其虚拟机,但进行了多方面的优化,提高了整体性能。 压缩包内的"ruby-2.0.0-p0"可能包含了完整的Ruby解释器、标准库、开发工具以及相关的...

    ruby-2.2.4

    在性能方面,Ruby 2.2.4增强了YARV(Yet Another Ruby VM)虚拟机,提升了代码执行速度。同时,它还修复了一些已知的安全漏洞,提高了整体的系统稳定性。 Ruby 2.2.4还支持新的语法特性,如Numeric Literal Ranges...

    Ruby-RubiniusRuby编程语言的实现

    首先, Rubinius 包含了一个虚拟机(VM),它是执行Ruby代码的基础。虚拟机设计的目标是解析并执行Ruby的字节码,这使得Ruby程序能够在不同的平台上运行,而无需针对每个平台重新编译。这个虚拟机的设计是高度优化的...

    ruby_2_features:自2.0版以来添加的Ruby功能的简单示例

    包括更快的字典查找,更高效的垃圾回收机制,以及对Ruby VM的其他内部改进。 10. **Ruby的版本管理(Ruby Version Manager)** Ruby 2.0发布的同时,rvm(Ruby Version Manager)和rbenv等版本管理工具也得到了...

    tuttle:Rails运行时配置检查器

    特征安装在正在运行的应用程序中的Web仪表板Rails常规配置,包括默认值库和框架的运行时内部(例如ActiveRecord查询缓存) 控制器和模型的应用清单宝石装Ruby VM运行时信息(GC统计信息,调整参数) 可选的分析...

    如何监控JRuby脚本的执行

    在使用jprofiler时,我们可以在VM arguments中加入这些参数,包括JRuby的配置选项,例如: - `-server`:指定使用服务器版本的JVM,优化性能。 - `-Xmx378m`:设置最大堆内存为378MB。 - `-Xss1024k`:设置每个线程...

    jdk1.6新特性 下载

    1. **改进的编译器** - JDK 1.6中的编译器,也称为HotSpot VM,进行了优化,提供了更快的代码生成和更高效的运行时性能。它增强了类型推断,使得Java语言在处理泛型时更加智能。 2. **动态语言支持** - JDK 1.6引入...

Global site tag (gtag.js) - Google Analytics