【问题标题】:Why doesn't this Ruby program return off heap memory to the operating system?为什么这个 Ruby 程序不将堆内存返回给操作系统?
【发布时间】:2015-12-31 15:13:39
【问题描述】:

我试图了解从 Ruby 堆分配的内存何时返回给操作系统。我知道 Ruby 永远不会返回分配给它的堆的内存,但我仍然不确定堆外内存的行为。即那些不适合 40 字节 RVALUE 的对象。

考虑以下程序,它分配一些大字符串,然后强制执行一次主要 GC。

require 'objspace'

STRING_SIZE = 250

def print_stats(msg)
  puts '-------------------'
  puts msg
  puts '-------------------'
  puts "RSS: #{`ps -eo rss,pid | grep #{Process.pid} | grep -v grep | awk '{ print $1,"KB";}'`}"
  puts "HEAP SIZE: #{(GC.stat[:heap_sorted_length] * 408 * 40)/1024} KB"
  puts "SIZE OF ALL OBJECTS: #{ObjectSpace.memsize_of_all/1024} KB"
end

def run
  print_stats('START WORK')
  @data=[]
  600_000.times do
    @data <<  " "  * STRING_SIZE
  end
  print_stats('END WORK')
  @data=nil
end

run
GC.start
print_stats('AFTER FORCED MAJOR GC')

在 MRI 上使用 Ruby 2.2.3 运行此程序会产生以下输出。强制执行 Major GC 后,堆大小与预期一致,但 RSS 并没有显着减少。

-------------------
START WORK
-------------------
RSS: 7036 KB
HEAP SIZE: 1195 KB
SIZE OF ALL OBJECTS: 3172 KB
-------------------
END WORK
-------------------
RSS: 205660 KB
HEAP SIZE: 35046 KB
SIZE OF ALL OBJECTS: 178423 KB
-------------------
AFTER FORCED MAJOR GC
-------------------
RSS: 164492 KB
HEAP SIZE: 35046 KB
SIZE OF ALL OBJECTS: 2484 KB

当我们分配一个大对象而不是许多小对象时,将这些结果与以下结果进行比较。

def run
  print_stats('START WORK')
  @data = " " * STRING_SIZE * 600_000
  print_stats('END WORK')
  @data=nil
end

-------------------
START WORK
-------------------
RSS: 7072 KB
HEAP SIZE: 1195 KB
SIZE OF ALL OBJECTS: 3170 KB
-------------------
END WORK
-------------------
RSS: 153584 KB
HEAP SIZE: 1195 KB
SIZE OF ALL OBJECTS: 149064 KB
-------------------
AFTER FORCED MAJOR GC
-------------------
RSS: 7096 KB
HEAP SIZE: 1195 KB
SIZE OF ALL OBJECTS: 2483 KB

注意最终的 RSS 值。我们似乎已经释放了我们为大字符串分配的所有内存。

我不确定为什么第二个示例会释放内存,但第一个示例不会,因为它们都从 Ruby 堆中分配内存。这是一个reference,可以提供解释,但我会对其他人的解释感兴趣。

将内存释放回内核也是有代价的。用户空间内存 分配器可能会(私下)保留该内存,希望它可以 在同一进程中重用,并且不将其返回给内核 在其他进程中使用。

【问题讨论】:

  • 订阅此线程。我也对这个非常感兴趣。
  • 根本区别在于第一个示例,其中创建了 600k 个 new 对象,而第二个示例中只有一个。虽然 referenced 数据的总大小相同,但第一个示例需要 60 万倍以上的插槽来引用对象(这些对象可能永远不会或很久以后回收到 OS)。
  • 我建议关注article 并链接explanationRVALUEs。我不确定他们是否完全正确,只有 Koichi aka ko1 自己可能知道。或者是一些坚定的爱好者,正在分析 Ruby 源代码。

标签: ruby memory-management mri


【解决方案1】:

@joanbm 有一个很好的观点。他引用了article explains this pretty well

Ruby 的 GC 会逐渐释放内存,因此当您对 1 个引用指向的 1 个大内存块执行 GC 时,它会释放所有内存,但是当引用很多时,GC 会以较小的块释放内存。

GC.start 的多次调用将在第一个示例中释放越来越多的内存。


这里有 2 篇文章可以深入挖掘:

【讨论】:

    猜你喜欢
    • 2018-01-14
    • 1970-01-01
    • 2015-03-12
    • 2017-09-14
    • 2014-11-25
    • 2012-08-16
    • 2014-04-14
    • 1970-01-01
    相关资源
    最近更新 更多