object space
rb_objspace_t
RVALUE
heap
rb_heap_t
heap page
heap_page_body
heap_page_header
heap_page_body
heap_page
memory managementHeap initialization (Init_heap)
Reference resources Ruby 2.x source code learning bootstrap The Ruby interpreter initializes the heap by calling the Init_heap function at startup
// 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); ... }
A slot corresponds to a RVALUE, and gc_params.heap_init_slots is the number of RVALUE objects that are initially idle for the interpreter, and HEAP_PAGE_OBJ_LIMIT is the number of RVALUE objects that a heap page can accommodate, so dividing the two into the number of heap pages that need to be added initially
// 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; }
Object memory allocation
How do new functions create objects
Let's use RubyVM::InstructionSequence to look at the virtual machine instructions generated by 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
When calling compile, the last one is compile options, which specifically uses fase to disable compile optimization, track the implementation of send instructions, and create objects that eventually fall into the newobj_of function.
// 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 condition judgment consists of two parts. The former part is used to determine whether an obj (RVALUE) can be allocated from the free list of eden heap using heap_get_free obj_head.
// 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 (trigger)
Visual analysis triggers GC when virtual machines cannot allocate space for new objects. Let's review an else branch of allocating space for objects.
// 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 (/* Objects can be allocated from the free list of heap page s */) { ... } else { return wb_protected ? newobj_slowpath_wb_protected(klass, flags, v1, v2, v3, objspace): newobj_slowpath_wb_unprotected(klass, flags, v1, v2, v3, objspace); } }
Both functions selected by wb_protected will eventually call 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)) { // Object allocation in GC process is considered BUG if (during_gc) { dont_gc = 1; during_gc = 0; rb_bug(...); } // If the ruby_gc_stress flag is set, GC is forced every time an object is allocated. if (ruby_gc_stressful) { // GC function: garbage_collect if (!garbage_collect(objspace, FALSE, FALSE, FALSE, GPR_FLAG_NEWOBJ)) { rb_memerror(); } } } obj = heap_get_freeobj(objspace, heap_eden); ... return obj; }
We have found an entry that triggers GC: when the object cannot be allocated from heap page free list and the ruby_gc_streeful flag is set. Let's look at the heap_get_freeobj function again.
// gc.c static inline VALUE heap_get_freeobj(rb_objspace_t *objspace, rb_heap_t *heap) { RVALUE *p = heap->freelist; // Loop until RVALUE is successfully allocated 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); } } }
The while loop attempts to call heap_get_freeobj_from_next_freepage until freelist is available
// gc.c static RVALUE *heap_get_freeobj_from_next_freepage(rb_objspace_t *objspace, rb_heap_t *heap) { struct heap_page *page; RVALUE *p; // Loop call heap_prepare until heap free pages are available while (heap->free_pages == NULL) { heap_prepare(objspace, heap); } ... return p; }
heap_prepare function:
// gc.c static void heap_prepare(rb_objspace_t *objspace, rb_heap_t *heap) { // Keep cleaning up!!! #if GC_ENABLE_LAZY_SWEEP if (is_lazy_sweeping(heap)) { gc_sweep_continue(objspace, heap); } #endif // Continue marking!!! #if GC_ENABLE_INCREMENTAL_MARK else if (is_incremental_marking(objspace)) { gc_marks_continue(objspace, heap); } #endif // gc_start start start tag & clearance 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(); } }