前言
数据结构
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();
}
}
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。