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 を使用してこのプログラムを実行すると、次の出力が生成されます。強制メジャー 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
これらの結果を、多数の小さなオブジェクトではなく 1 つの大きなオブジェクトを割り当てた場合の次の結果と比較してください。
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 値に注意してください。大きな文字列に割り当てたすべてのメモリを解放したようです。
2 番目の例がメモリを解放する理由はわかりませんが、最初の例はどちらも Ruby ヒープからメモリを割り当てているため、そうではありません。これは説明を提供できる参考文献の 1 つですが、他の人からの説明に興味があります。
メモリを解放してカーネルに戻すことにもコストがかかります。ユーザー空間のメモリ アロケータは、同じプロセス内で再利用でき、他のプロセスで使用するためにカーネルに返さないことを期待して、そのメモリを (プライベートに) 保持する場合があります。