Ruby 2.x 源代码学习:内存管理 & GC
前言
数据结构
object space
rb_objspace_t
RVALUE
heap
rb_heap_t
heap page
heap_page_body
heap_page_header
heap_page_body
heap_page
内存管理
堆初始化(Init_heap)
参考 Ruby 2.x 源代码学习 bootstrap,Ruby 解释器在启动的时候会调用 Init_heap 函数初始化堆
// gc.c void Init_heap(void) { rb_objspace_t *objspace = &rb_objspace; ... heap_add_pages(objspace, heap_eden, gc_params.heap_init_slots / HEAP_PAGE_OBJ_LIMIT); ... }
一个 slot 对应一个 RVALUE,gc_params.heap_init_slots 是解释器初始空闲 RVALUE 对象个数,HEAP_PAGE_OBJ_LIMIT 是一个 heap page 能够容纳的 RVALUE 对象的个数,所以两者一除就得到初始时需要添加多少个 heap page
// gc.c static void heap_add_pages(rb_objspace_t *objspace, rb_heap_t *heap, size_t add) { size_t i; heap_allocatable_pages = add; heap_pages_expand_sorted(objspace); for (i = 0; i < add; i++) { heap_assign_page(objspace, heap); } heap_allocateable_pages = 0; }
对象内存分配
new 函数如何创建对象
我们使用 RubyVM::InstructionSequence 看看 String.new 生成的虚拟机指令
irb> code =<<EOF irb> s = String.new irb> EOF irb> puts RubyVM::InstructionSequence.compile(code, '', '', 0, false) == disasm: <RubyVM::InstructionSequence:<compiled>@>==================== local table (size: 2, argc: 0 [opts: 0, rest: -1, post: 0, block: -1] s1) [ 2] s 0000 putnil 0001 getconstant :String 0003 send <callinfo!mid:new, argc:0, ARGS_SKIP> 0005 dup 0006 setlocal s, 0 0009 leave => nil
在调用 compile 的时候最后一个是 compile options,这里特意使用了 fase 来禁用编译优化,追踪 send 指令的实现,创建对象最终落到 newobj_of 函数
// gc.c static inline VALUE newobj_of(VALUE klass, VALUE flags, VALUE v1, VALUE v2, VALUE v3, int wb_protected) { rb_objspace_t *objspace = &rb_objspace; VALUE obj; if (!(during_gc || ruby_gc_stressful || gv_event_hook_available_p(objspace)) && (obj = heap_get_freeobj_head(objspace, heap_eden)) != False) { return newobj_init(klass, v1, v2, v3, wb_protected, objspace, obj); } else { ... } }
if 条件判断包含两部分,前一部分用来判断是否可以使用 heap_get_freeobj_head 从 eden heap 的 free list 中分配一个 obj(RVALUE)
// gc.c static inline VALUE heap_get_freeobj_head(rb_objspace_t *objspace, rb_heap_t *heap) { RVALUE *p = heap->freelist; if (LIKELY(p != NULL)) { heap->freelist = p->as.free.next; } return (VALUE)p; }
GC
gc params
start(触发)
直观分析,当虚拟机无法为新对象分配空间时会触发 GC,我们回顾一下为对象分配空间的的一个 else 分支
// gc.c static inline VALUE newobj_of(VALUE klass, VALUE flags, VALUE v1, VALUE v2, VALUE v3, int wb_protected) { rb_objspace_t *objspace = &rb_objspace; VALUE obj; if (/* 可以从 heap page 的 free list 中分配对象 */) { ... } else { return wb_protected ? newobj_slowpath_wb_protected(klass, flags, v1, v2, v3, objspace): newobj_slowpath_wb_unprotected(klass, flags, v1, v2, v3, objspace); } }
wb_protected 选择的两个函数最终都会调用 newobj_slowpath
// gc.c static inline VALUE newobj_slowpath(VALUE klass, VALUE flags, VALUE v1, VALUE v2, VALUE v3, rb_objspace_t *objspace, int wb_protected) { VALUE obj; if (UNLIKELY(during_gc || ruby_gc_streeful)) { // 在 GC 过程中进行对象分配被认为是 BUG if (during_gc) { dont_gc = 1; during_gc = 0; rb_bug(...); } // 如果设置了 ruby_gc_stressful 标志则在每次对象分配时都强迫进行 GC if (ruby_gc_stressful) { // GC 函数: garbage_collect if (!garbage_collect(objspace, FALSE, FALSE, FALSE, GPR_FLAG_NEWOBJ)) { rb_memerror(); } } } obj = heap_get_freeobj(objspace, heap_eden); ... return obj; }
我们已经找到了触发 GC 的一个入口:当无法从 heap page free list 分配对象且设置了 ruby_gc_streeful 标志时。我们再来看看 heap_get_freeobj 函数
// gc.c static inline VALUE heap_get_freeobj(rb_objspace_t *objspace, rb_heap_t *heap) { RVALUE *p = heap->freelist; // 循环,直到成功分配 RVALUE while (1) { if (LIKELY(p != NULL)) { heap->freelist = p->as.free.next; return (VALUE)p; } else { p = heap_get_freeobj_from_next_freepage(objspace, heap); } } }
while 循环会尝试调用 heap_get_freeobj_from_next_freepage 直到 freelist 可用
// gc.c static RVALUE *heap_get_freeobj_from_next_freepage(rb_objspace_t *objspace, rb_heap_t *heap) { struct heap_page *page; RVALUE *p; // 循环调用 heap_prepare 直到 heap free pages 可用 while (heap->free_pages == NULL) { heap_prepare(objspace, heap); } ... return p; }
heap_prepare 函数:
// gc.c static void heap_prepare(rb_objspace_t *objspace, rb_heap_t *heap) { // 继续清理!!! #if GC_ENABLE_LAZY_SWEEP if (is_lazy_sweeping(heap)) { gc_sweep_continue(objspace, heap); } #endif // 继续标记!!! #if GC_ENABLE_INCREMENTAL_MARK else if (is_incremental_marking(objspace)) { gc_marks_continue(objspace, heap); } #endif // gc_start 开始标记&清除 if (heap->free_pages == NULL && (will_be_incremental_marking(objspace) || heap_increment(objspace, heap) == FALSE) && gc_start(objspace, FALSE, FALSE, FALSE, GPR_FLAG_NEWOBJ) == FALSE) { rb_memerror(); } }