參考 Ruby 2.x 源代碼學習 bootstrap,Ruby 解釋器在啓動的時候會調用 Init_heap 函數初始化堆bootstrap
// 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 pagesegmentfault
// 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; }
咱們使用 RubyVM::InstructionSequence 看看 String.new 生成的虛擬機指令ruby
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)jsp
// 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,咱們回顧一下爲對象分配空間的的一個 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_slowpathpost
// 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 函數:spa
// 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(); } }