为什么这个Ruby程序不会将堆内存返回给操作系统?

Ada*_*dam 20 ruby memory-management mri

我试图了解从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')
Run Code Online (Sandbox Code Playgroud)

在MRI上使用Ruby 2.2.3运行该程序,它产生以下输出.在强制主要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
Run Code Online (Sandbox Code Playgroud)

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

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
Run Code Online (Sandbox Code Playgroud)

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

我不确定为什么第二个示例释放内存但第一个示例没有,因为它们都在Ruby堆上分配内存.这是一个可以提供解释的参考,但我会对其他人的解释感兴趣.

将内存释放回内核也需要付出代价.用户空间内存分配器可以(私下)保留在该内存上,希望它可以在同一进程中重用,而不是将其返回给内核以便在其他进程中使用.

gdu*_*lle 3

@joanbm 这里有一个很好的观点。他引用的文章很好地解释了这一点

Ruby 的 GC 会逐渐释放内存,因此当您对 1 个引用指向的 1 大块内存进行 GC 时,它会释放全部内存,但是当存在大量引用时,GC 会以较小的块释放内存。

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


这里还有另外两篇文章可以深入研究: