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

对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  
简单粗暴是针对问题最直接的解决办法。

相关推荐

    RubyVM-开源

    RubyVM的设计目标是提供一个可扩展且高效的平台,让开发者能够深入理解Ruby的内部工作原理,并可以根据需要对其进行定制和优化。这个“乐高玩具套装”的比喻意味着RubyVM允许开发者像拼装积木一样,自由组合和构建...

    Ruby-rubybuild编译和安装Ruby

    在日常开发中,`rbenv`和`ruby-build`组合提供了对Ruby版本的精细控制,使得开发者可以轻松地在不同项目之间切换,避免了版本冲突的问题。此外,它们还支持安装一些特定的patched Ruby版本,如JRuby和Truffleruby,...

    Ruby Ruby Ruby Ruby Ruby Ruby

    Ruby Ruby Ruby Ruby Ruby Ruby

    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...

    ruby2.1.6安装文件

    例如,YARV(Yet Another Ruby VM)虚拟机的优化使得代码执行更高效。 2. **钻石操作符(>>>)**:Ruby 2.1引入了钻石操作符,用于解决多重继承中的方法解析顺序问题。这个符号使得类可以明确指定从哪个父类继承...

    ruby DBI ruby DBI ruby DBI

    ruby DBI ruby DBI ruby DBIruby DBI ruby DBI ruby DBIruby DBI ruby DBI ruby DBIruby DBI ruby DBI ruby DBIruby DBI ruby DBI ruby DBIruby DBI ruby DBI ruby DBIruby DBI ruby DBI ruby DBIruby DBI ruby DBI ...

    Ruby Under a Microscope 在c语言层面深入挖掘ruby

    在Ruby的早期版本中,这个虚拟机被称为YARV(Yet Another Ruby VM),它是Ruby 1.9版本的虚拟机。书中对Ruby 2.x、1.9和1.8版本都有所覆盖,让读者可以了解到Ruby发展的不同阶段。 另一个值得探讨的部分是Ruby的...

    tracecap-ruby-profiler:通过USDT导出Ruby VM(MRI)堆栈和对象分配计数跟踪以进行tracecap提取

    通过USDT导出Ruby VM(MRI)堆栈和对象分配计数跟踪,以进行tracecap提取。 分析代码的设计和实现很大程度上受启发。 安装 将此行添加到您的应用程序的Gemfile中: gem 'tracecap_profiler' 然后执行: $ bundle...

    ruby-1.9.3-p547.tar.gz

    这个版本对Ruby的语法进行了改进,包括更好的错误处理机制,增强了Unicode支持,以及对YARV(Yet Another Ruby VM)虚拟机的优化,使得代码执行速度有所提升。此外,1.9.3还引入了新的垃圾回收机制,提高了内存管理...

    Ruby Hack Guide中文版.chm

    Ruby Hacking Guide是一本探讨C Ruby实现的书,这次发布的部分包括对全书的介绍和本书的第一部分。第一部分的内容包括对Ruby语言一个概要介绍和对Ruby对象模型的讲解。从我个人阅读的感觉来看,第一章对于Ruby语言的...

    ruby2ruby.zip

    ruby2ruby 提供一些用来根据 RubyParser 兼容的 Sexps 轻松生成纯 Ruby 代码的方法。可在 Ruby 中轻松实现动态语言处理。 标签:ruby2ruby

    Ruby资源ruby-v3.1.1.zip

    本资源“ruby-v3.1.1.zip”包含了Ruby的最新版本3.1.1,这是一个重要的里程碑,因为它引入了新特性、性能优化以及对旧版本的改进。 在Ruby 3.1.1中,开发者可以期待以下关键特性: 1. **块参数解构**:Ruby 3.1...

    Ruby完全自学手册

    Ruby是一种简洁而功能强大的编程语言,由日本的松本行弘(Yukihiro "Matz" Matsumoto)在1993年开发,并于...此外,由于技术不断更新,保持对最新Ruby版本的关注,以及学习相关的最佳实践和安全知识也是至关重要的。

    Ruby完全自学手册 下

    《Ruby完全自学手册》是一本完全覆盖Ruby和Ruby on Rails的完全自学手册。《Ruby完全自学手册》的特色是由浅入深、循序渐进,注重理论和实践的结合。虽然定位为入门手册,但是依然涉及许多高级技术和应用,覆盖到的...

    Ruby-rubyinstall安装RubyJRubyRubiniusMagLevorMRuby

    Ruby是一种强大的、面向对象的脚本语言,广泛用于Web开发、服务器端编程和各种应用程序。在Ruby的世界里,管理不同的Ruby实现(如MRI、JRuby、Rubinius、MagLev和MRuby)是非常重要的,这有助于开发者根据项目需求...

    src-oepkgs/ruby-ruby2ruby

    src-oepkgs/ruby-ruby2rubysrc-oepkgs/ruby-ruby2rubysrc-oepkgs/ruby-ruby2rubysrc-oepkgs/ruby-ruby2rubysrc-oepkgs/ruby-ruby2rubysrc-oepkgs/ruby-ruby2rubysrc-oepkgs/ruby-ruby2rubysrc-oepkgs/ruby-ruby2...

Global site tag (gtag.js) - Google Analytics