Ruby 遇到一个垃圾回收的问题,有 CentOS 服务器的朋友帮忙确认一下?

blacklee · 2018年12月10日 · 最后由 dsh0416 回复于 2018年12月11日 · 1587 次阅读

非常简单的一个脚本,运行两次看结果

require 'json'

class Numeric
  def to_hmsize
    if self > 1024 * 1024
      "#{(self * 1.0 / (1024 * 1024)).round(2)} MB"
    end
  end
  def to_hmtime
    min = self % 3600 / 60
    sec = self % 60
    str = ""
    str = "#{min}m" if min > 0
    str = "#{str}#{sec}s" if sec > 0
    str
  end
end

file = File.expand_path("../wiki_pages.json", __FILE__)
if !File.exists?(file)
  open(file, 'w') do |f|
    array = []
    700000.times.each do |i|
      array << {pageid: i, title: "title of #{i}", pagename: "pagename_of_#{i}"}
    end
    f.puts array.to_json
  end
  puts "created a json file: #{File.size(file)}[#{File.size(file).to_hmsize}]"
  puts "init the json file at the first launch, please run again."
  exit
end

puts RUBY_DESCRIPTION

require 'objspace'

Thread.new do
  started = Time.now.to_i
  while true
    sys_mem = (`ps -o rss= -p #{Process.pid}`.strip.to_i * 1024).to_hmsize
    objspace_mem = ObjectSpace.memsize_of_all.to_hmsize
    puts "#{(Time.now.to_i - started).to_hmtime}\tsys_mem:#{sys_mem}, objspace_mem:#{objspace_mem}"
    sleep 3
    GC.start
  end
end

sleep 10

puts "read content from file #{File.size(file)}[#{File.size(file).to_hmsize}]"
json = IO.read(file)
sleep 30

puts "parse json string to object"
obj = JSON.parse(json)
sleep 30

puts "set string&object to nil, and sleep 30 min to check the memory usage."
json = nil
obj = nil

sleep 1800

我测试出来的结果是,在 Mac 上,进程的内存占用以 12M 开始,峰值 400M,30 分钟后退出时 45M 左右。但是在 CentOS 上,垃圾回收到 280M 就不会再下降了。

Ruby 版本 2.5.1 和 2.5.3 的结果一样,Ruby 是用 rvm 安装的。

准备给开发组报 bug 了,谁有空先帮忙验证一下。

统计的时候能不能跑一下 GC.stat,这样看不出来是哪里的内存。

用 CentOS 跑了一下

[root@6d454a5b971b /]# ruby -v
ruby 2.5.3p105 (2018-10-18 revision 65156) [x86_64-linux]
[root@6d454a5b971b /]# vi gc.rb
[root@6d454a5b971b /]# ruby gc.rb
created a json file: 52866672[50.42 MB]
init the json file at the first launch, please run again.
[root@6d454a5b971b /]# ruby gc.rb
ruby 2.5.3p105 (2018-10-18 revision 65156) [x86_64-linux]
    sys_mem:9.48 MB, objspace_mem:4.15 MB
3s  sys_mem:9.62 MB, objspace_mem:3.46 MB
6s  sys_mem:9.63 MB, objspace_mem:3.46 MB
9s  sys_mem:9.63 MB, objspace_mem:3.46 MB
read content from file 52866672[50.42 MB]
12s sys_mem:60.12 MB, objspace_mem:53.88 MB
15s sys_mem:60.12 MB, objspace_mem:53.88 MB
18s sys_mem:60.12 MB, objspace_mem:53.88 MB
21s sys_mem:60.12 MB, objspace_mem:53.88 MB
24s sys_mem:60.12 MB, objspace_mem:53.88 MB
27s sys_mem:60.12 MB, objspace_mem:53.88 MB
30s sys_mem:60.12 MB, objspace_mem:53.88 MB
33s sys_mem:60.12 MB, objspace_mem:53.88 MB
36s sys_mem:60.12 MB, objspace_mem:53.88 MB
39s sys_mem:60.12 MB, objspace_mem:53.88 MB
parse json string to object
43s sys_mem:360.47 MB, objspace_mem:322.96 MB
46s sys_mem:360.54 MB, objspace_mem:322.96 MB
50s sys_mem:360.54 MB, objspace_mem:322.96 MB
53s sys_mem:360.54 MB, objspace_mem:322.96 MB
56s sys_mem:360.54 MB, objspace_mem:322.96 MB
1m  sys_mem:360.54 MB, objspace_mem:322.96 MB
1m3s    sys_mem:360.54 MB, objspace_mem:322.96 MB
1m7s    sys_mem:360.54 MB, objspace_mem:322.96 MB
1m10s   sys_mem:360.54 MB, objspace_mem:322.96 MB
set string&object to nil, and sleep 30 min to check the memory usage.
1m13s   sys_mem:304.78 MB, objspace_mem:3.46 MB
1m16s   sys_mem:304.78 MB, objspace_mem:3.46 MB
1m19s   sys_mem:304.78 MB, objspace_mem:3.46 MB
1m22s   sys_mem:304.78 MB, objspace_mem:3.46 MB

几秒钟就释放干净了。你的启动参数是怎样的?

dsh0416 回复

启动 ruby 没有设置参数,就默认的。

你这个结果也是没有释放干净啊,就 objspace 里面是干净的,但 system memory 的占用应该要下降到 50M 以内才对。

blacklee 回复

你测 sys_mem 的方法是 RSS,对 RSS 的定义如下

Resident Set Size = how much memory is allocated to that process and is in RAM (including shared memory!). This includes all stack and heap memory and shared libraries, as long as they are in RAM. It does not include memory which is swapped out.

操作系统会通过 shared memory 对内存进行缓冲(buffered),以使得重启线程或程序后能更快加载,这是 Linux 的内存管理特性,只有当系统本身内存不够用时,才会去释放这部分 buffered 的内存。程序本身不能主动释放自己的缓存内存。

如果你使用 htop 查看进程的话,内存里黄色的和蓝色的这块,就分别是 buffered 和 cached 内存。

Linux 倾向于不直接释放,并吃满内存以做缓存加速系统运行,只有当内存不够时再去释放掉。这一点在 Windows 上从 Windows Vista 中也引入了这一特性。Mac 上不出意外也是有的,只是因为你一直在用,所以被慢慢释放掉了。

dsh0416 回复

感谢解疑。我去多看看资料。

dsh0416 回复

希望我没有进入「我就是要证明我的观察是对的」那种魔怔。

我又做了个测试,麻烦帮看看测试思路是否不对:

模拟系统内存吃紧的情况,再看看这个内存是否会被释放掉。测试脚本基本不变,就改那个生成 json 文件的大小的数值(测试的 ruby)。 这一次测试的机器内存 4G,我把那个数值从 70W 改成 100W,这样生成的文件大小就变成了 73M。 然后开四个窗口每隔半分钟运行一个脚本,当三个进程都把字符串 parse 成对象后,内存就爆满了。 等第四个进程开始执行 JSON.parse 时,预期反应是之前设置为空了的进程的内存开始释放,结果是系统随意拿起个进程直接 Kill 掉了。(如果不想等到第 4 个进程,可以更改生成 json 文件大小的那个数值)

blacklee 回复

Linux 对内存极端不够的场景确实是会直接 kill 掉的,这一点非常。。。但 Linux 有一个指令可以清空掉 buffer 和 cache 的内存来方便调试,你可以找一下。

blacklee 回复

我测了一下。用另一个 terminal 执行 sync; echo 3 > /proc/sys/vm/drop_caches 可以把 cache drop 掉

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