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 函數初始化堆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;
}

對象內存分配

new 函數如何建立對象

咱們使用 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

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_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();
    }
}

mark(標記)

sweep(清除)

總結

相關文章
相關標籤/搜索