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

mark(标记)

sweep(清除)

总结

相关推荐