Ruby 2.1 RGenGC

要搞就搞明白点, 一知半解不如不知

Posted on June 18, 2015

RGenGC

  • ruby 对象堆内存分割为slots, 每个slot存储一个RVALUE, 每个RVALUE占用40 bytes

    GC::INTERNAL_CONSTANTS[:RVALUE_SIZE] => 40

  • ruby 2.1 GC RGenGC: 标记清除 + 分代

  • 分代理论基础:

    大部分新对象存活期短, 新对象更可能包含老对象的引用(不会影响新对象成为垃圾), 而只有少部分新对象会被老对象引用(会影响新对象的回收)

    遍历新对象集会有大量垃圾可以回收, 而遍历老对象释放的内存很少

  • 新对象提升为老对象: (一般情况) 在新对象标记清除中存留的对象, 提升为老对象

  • 问题1: minor GC会认为 只被老对象引用的新对象 是垃圾, 因为minor GC不遍历老对象

    方案: remember set + write-barrier

  • write-barrier: 当老对象写入新对象的引用时, 会触发write-barrier, 将老对象放入remember set

  • remember set: 存储指向新对象的老对象集合, 以及老对象引用的shady对象

  • GC 类别:

    • minor GC: 只遍历新对象集 + remember set
    • major GC: 遍历所有对象集
  • 问题2: C API 创建的对象无法有效的实现write-barrier

    方案: sunny + shady

  • sunny 对象:

    实现write-barrier

    允许被提升为老对象

    shade操作: 某些C API会使sunny 对象write-barrier失效, 此时sunny 对象将变为shady, (必要的话)从老对象中去除加人remember set

  • shady 对象:

    没有实现write-barrier

    不允许提升为老对象(因为老对象需要write-barrier来避免问题1)

    shady对象如果被老对象引用, 将被GC放入remember set (如何理解?)

  • page/slot

    ruby 对象内存包含分为多个page, 每个page分为多个slot

  • Eden/Tomb

    page 分为Eden/Tomb

    • Eden: 包含至少一个存活对象的page
    • Tomb: 没有存活对象的page

GC.stat

  • GC.stat[:count] GC 执行总次数, major 加 minor

  • GC.stat[:minor_gc_count] minor GC 执行次数

  • GC.stat[:major_gc_count] major GC 执行次数

  • GC.stat[:total_allocated_object] 整个生命周期中为对象分配内存的总次数

  • GC.stat[:total_freed_object] 整个生命周期中为对象清除内存的总次数

  • GC.stat[:heap_length] 当前page总页数, 但并不是全都在使用

    GC::INTERNAL_CONSTANTS[:HEAP_OBJ_LIMIT] => 408 每页slot个数

    GC::INTERNAL_CONSTANTS[:RVALUE_SIZE] => 40

  • GC.stat[:heap_used] 在使用的page数, 包括存活对象和清除的slot

    GC.stat[:heap_used] = GC.stat[:heap_eden_page_length] + GC.stat[:heap_tomb_page_length]

  • GC.stat[:heap_eden_page_length] eden page 个数

  • GC.stat[:heap_tomb_page_length] tomb page 个数

  • GC.stat[:heap_live_slot] 当前存活的对象

    GC.stat[:heap_live_slot] = GC.stat[:total_allocated_object] - GC.stat[:total_freed_object]

    GC.stat[:heap_live_slot] / 408 约等于 GC.stat[:heap_used] (不对吧?)

    感觉应该是: GC.stat[:heap_live_slot] / 408 约等于(小于等于) GC.stat[:heap_eden_page_length]

  • GC.stat[:heap_free_slot] 没使用/已经清除的slot

    GC.stat[:heap_free_slot] / 408 约等于(大于等于) GC.stat[:heap_tomb_page_length]

  • GC.stat[:heap_final_slot] TODO

  • GC.stat[:heap_swept_slot] 每页清除前置零; 每页清除后, 当页可用的slot个数

  • GC.stat[:heap_increment] 计算需要添加新slot的个数, 动态变化

    heap_increment = (current_number_of_slots * factor) - current_number_of_slots

    factor 默认1.8

  • GC.stat[:remembered_shady_object] remember set 中shady个数, 每次major GC会重算

  • GC.stat[:remembered_shady_object_limit]

    remember set 中shady个数阈值, 超过阈值将触发major GC

    动态阈值, remembered_shady_object_limit = factor * remembered_shady_object

    factor默认2.0

  • GC.stat[:old_object] 老对象个数

  • GC.stat[:old_object_limit]

    动态阈值, 老对象个数超过阈值将触发major GC, 公式同remembered_shady_object_limit

  • GC.stat[:malloc_increase]

    不是所有对象都满足40 byte大小, 超过40 byte大小的对象可用使用malloc(2)单独申请内存, 此值表示此种非ruby heap内存的大小

  • GC.stat[:malloc_limit]

    动态阈值, GC.stat[:malloc_increase] 超过阈值将触发minor GC

    公式: malloc_limit = factor * malloc_increase factor 默认1.4

  • GC.stat[:oldmalloc_increase] GC.stat[:malloc_increase]中老对象占用量

  • GC.stat[:oldmalloc_limit]

    动态阈值, GC.stat[:oldmalloc_increase] 将触发major GC

    公式: oldmalloc_limit = factor * oldmalloc_increase factor默认1.2


参考资料


TODO

Ruby 2.2 中的增量式垃圾回收