Ruby 2.x source code learning: memory management & GC

Preface data structure object space rb_objspace_t RVALUE heap rb_heap_t heap page heap_page_body heap_page_header heap_page_body heap_page memory mana...
object space
RVALUE
heap
heap page
Heap initialization (Init_heap)
Object memory allocation
gc params
start (trigger)
mark
Swep (Clearance)
Preface data structure

object space

rb_objspace_t

RVALUE

heap

rb_heap_t

heap page

heap_page_body

heap_page_header

heap_page_body

heap_page

memory management

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

mark

Swep (Clearance)

summary

6 April 2019, 22:36 | Views: 2892

Add new comment

For adding a comment, please log in
or create account

0 comments