源码深度Mono GC标记阶段逐行解读元数据系列Unity和C#的GC剖析与对比章节04-源码深度-Mono GC编号04-03标签Mono, SGen, 标记阶段, Mark Stack, 对象图遍历, 灰色节点, 并行标记阅读时间~35分钟依赖知识04-01数据结构、04-02分配路径概述标记阶段Mark Phase是所有追踪式GC的核心。SGen的标记阶段从根集合出发遍历整个对象引用图将所有可达对象标记为存活。本文逐层解读SGen标记阶段的源码实现包括根集合扫描、Mark Stack的push/pop操作、对象图遍历中的描述符解码、灰色节点处理、以及并行标记的工作窃取机制。标记阶段总览标记阶段在GC周期中的位置GC周期: 1. STW (Stop-The-World) 2. 标记阶段 (Mark Phase) -- 本文重点 a. 扫描根集合 b. 遍历对象图 (灰色-黑色) 3. 清扫阶段 (Sweep Phase) 4. [可选] 压缩阶段 (Compact Phase) 5. 恢复线程标记阶段入口// sgen-mark.c void sgen_mark_phase (void) { SgenGC *gc sgen_gc; // 重置标记栈 sgen_mark_stack_clear(gc-mark_stack); // 步骤1扫描根集合 sgen_mark_scan_roots(); // 步骤2扫描Remembered Set分代GC if (gc-current_collection_mode SGEN_GC_MODE_NURSERY) { sgen_mark_scan_remset(); } // 步骤3处理标记栈遍历对象图 if (gc-gc_thread_count 1) { sgen_mark_parallel(); } else { sgen_mark_serial(); } // 步骤4处理特殊对象 sgen_mark_finalize_objects(); // 含Finalizer的对象 sgen_mark_weak_refs(); // 弱引用处理 // 统计 gc-stats.marked_objects gc-mark_stack-total_pushed; gc-stats.mark_time mono_100ns_ticks() - gc-stats.mark_start; }根集合扫描根集合的组成// sgen-roots.c typedef enum { ROOT_TYPE_STATIC 0x01, // 静态字段 ROOT_TYPE_THREAD 0x02, // 线程栈 ROOT_TYPE_HANDLE 0x04, // GC Handle ROOT_TYPE_FINALIZE 0x08, // Finalizer队列 ROOT_TYPE_PINNED 0x10, // 固定对象 ROOT_TYPE_MANAGED 0x20, // 托管分配器注册的根 } SgenRootType; typedef struct _SgenRootDesc { SgenRootType type; gpointer *start; // 根区域起始地址 gpointer *end; // 根区域结束地址 const char *source; // 描述调试用 } SgenRootDesc; // 全局根列表 static GPtrArray *sgen_roots;扫描根集合// sgen-roots.c void sgen_mark_scan_roots (void) { // 遍历所有注册的根 for (int i 0; i sgen_roots-len; i) { SgenRootDesc *root g_ptr_array_index(sgen_roots, i); switch (root-type) { case ROOT_TYPE_STATIC: sgen_mark_scan_static_roots(root); break; case ROOT_TYPE_THREAD: sgen_mark_scan_thread_roots(root); break; case ROOT_TYPE_HANDLE: sgen_mark_scan_handle_roots(root); break; case ROOT_TYPE_FINALIZE: sgen_mark_scan_finalize_queue(root); break; case ROOT_TYPE_PINNED: // Pinned对象直接标记不扫描其引用 sgen_mark_pinned(root); break; } } }线程栈扫描// sgen-roots.c static void sgen_mark_scan_thread_roots (SgenRootDesc *root) { SgenThreadInfo *info; FOREACH_THREAD(info) { // 获取线程的栈范围 gpointer *stack_start (gpointer*)info-stack_start; gpointer *stack_end (gpointer*)info-stack_ptr; // 扫描栈上的每个指针大小的位置 for (gpointer *p stack_start; p stack_end; p) { gpointer value *p; // 保守检查是否指向堆内 if (sgen_is_heap_pointer(value)) { // 精确检查是否是有效对象 if (sgen_is_valid_object(value)) { sgen_mark_object(value); } } } // 扫描线程的寄存器上下文 // 保存的CPU寄存器中可能包含对象引用 sgen_mark_scan_registers(info-gc_context); } }静态字段扫描// sgen-roots.c static void sgen_mark_scan_static_roots (SgenRootDesc *root) { gpointer *start root-start; gpointer *end root-end; for (gpointer *p start; p end; p) { gpointer value *p; if (value sgen_is_heap_pointer(value)) { sgen_mark_object(value); } } }对象标记与Mark Stack标记单个对象// sgen-mark.c static inline void sgen_mark_object (gpointer obj) { // 检查是否已在NurseryMinor GC时跳过老年代对象 if (sgen_gc.current_collection_mode SGEN_GC_MODE_NURSERY !sgen_is_in_nursery(obj)) { return; // Minor GC不标记老年代对象 } // 检查是否已标记 if (SGEN_OBJECT_IS_MARKED(obj)) { return; // 已标记跳过 } // 设置标记位 SGEN_OBJECT_SET_MARKED(obj); // 压入标记栈变为灰色节点 sgen_mark_stack_push(sgen_gc.mark_stack, obj); }Mark Stack操作// sgen-mark-stack.c void sgen_mark_stack_push (SgenMarkStack *ms, gpointer obj) { // 检查容量 if (G_UNLIKELY(ms-top ms-limit)) { sgen_mark_stack_grow(ms); } *ms-top obj; ms-top; ms-size; } static void sgen_mark_stack_grow (SgenMarkStack *ms) { size_t old_capacity ms-capacity; size_t new_capacity old_capacity * 2; ms-stack (gpointer*)mono_valloc_realloc( ms-stack, new_capacity * sizeof(gpointer)); ms-limit ms-stack new_capacity; ms-capacity new_capacity; // top指针需要重新计算因为realloc可能移动了内存 ms-top ms-stack ms-size; } gpointer sgen_mark_stack_pop (SgenMarkStack *ms) { if (ms-size 0) return NULL; ms-top--; ms-size--; return *ms-top; } // 批量弹出优化缓存局部性 int sgen_mark_stack_pop_batch (SgenMarkStack *ms, gpointer *batch, int count) { int n MIN(count, ms-size); for (int i 0; i n; i) { ms-top--; batch[i] *ms-top; } ms-size - n; return n; }对象图遍历串行标记主循环// sgen-mark.c static void sgen_mark_serial (void) { SgenMarkStack *ms sgen_gc.mark_stack; gpointer obj; // 主循环处理所有灰色节点 while ((obj sgen_mark_stack_pop(ms)) ! NULL) { sgen_mark_scan_object(obj); } }扫描对象引用// sgen-mark.c static inline void sgen_mark_scan_object (gpointer obj) { MonoVTable *vt SGEN_OBJECT_VTABLE(obj); SgenDescriptor desc vt-gc_descr; int desc_type desc DESC_TYPE_MASK; switch (desc_type) { case DESC_TYPE_NONE: // 无引用字段无需扫描 break; case DESC_TYPE_SMALL: { // 小对象使用bitmap扫描 guint32 bitmap sgen_descriptor_get_bitmap(desc); gpointer *fields (gpointer*)((char*)obj sizeof(SgenObjectHeader)); while (bitmap) { int bit __builtin_ctz(bitmap); // 找最低位的1 gpointer ref fields[bit]; if (ref) { sgen_mark_object(ref); } bitmap (bitmap - 1); // 清除最低位 } break; } case DESC_TYPE_LARGE: { // 大对象多字bitmap guint32 *bitmap_ptr sgen_descriptor_get_bitmap_ptr(desc); int bitmap_words sgen_descriptor_get_bitmap_words(desc); gpointer *fields (gpointer*)((char*)obj sizeof(SgenObjectHeader)); for (int w 0; w bitmap_words; w) { guint32 word bitmap_ptr[w]; int base w * 32; while (word) { int bit __builtin_ctz(word); gpointer ref fields[base bit]; if (ref) { sgen_mark_object(ref); } word (word - 1); } } break; } case DESC_TYPE_COMPLEX: { // 复杂布局使用回调函数 SgenComplexScanFunc scan_func sgen_descriptor_get_complex_func(desc); scan_func(obj, sgen_mark_object); break; } case DESC_TYPE_VECTOR: { // 数组扫描元素 SgenArrayObj *arr (SgenArrayObj*)obj; MonoClass *elem_class mono_class_get_element_class(vt-klass); if (sgen_is_reference_type(elem_class)) { // 引用类型数组扫描每个元素 gpointer *elements (gpointer*)((char*)arr sizeof(SgenArrayObj)); int length arr-length; for (int i 0; i length; i) { if (elements[i]) { sgen_mark_object(elements[i]); } } } // 值类型数组如果元素含引用使用描述符扫描 break; } case DESC_TYPE_STRING: // 字符串无引用char数组内联无需扫描 break; } }Bitmap扫描的位操作优化// sgen-mark.c // __builtin_ctz: Count Trailing Zeros // 在x86上编译为BSF指令单周期完成 // 伪代码展示bitmap扫描过程 // 假设bitmap 0b00001010 (引用在第1、3位) // // 第1次迭代: // bit ctz(0b00001010) 1 // 扫描 fields[1] // bitmap (bitmap-1) 0b00001010 0b00001001 0b00001000 // // 第2次迭代: // bit ctz(0b00001000) 3 // 扫描 fields[3] // bitmap (bitmap-1) 0b00001000 0b00000111 0b00000000 // // 循环结束共扫描2个引用字段灰色节点处理三色标记不变量SGen使用三色标记算法白色未访问的对象未设置mark bit灰色已标记但引用未扫描在mark stack中黑色已标记且引用已扫描mark bit已设置已从stack弹出// 不变量维护 // 1. 黑色对象的所有引用都指向灰色或黑色对象 // 2. 灰色对象在mark stack中等待处理 // 3. 白色对象不可达将被回收 // sgen_mark_object: 白色 - 灰色 (设置mark bit push stack) // sgen_mark_scan_object: 灰色 - 黑色 (扫描引用 pop from stack)标记过程中的对象移动问题在SGen中标记阶段不会移动对象Mark-Sweep模式因此不存在标记过程中对象地址变化的问题。但在Copying模式下需要特殊处理// sgen-mark.c (Copying模式) static inline void sgen_mark_object_copying (gpointer obj) { if (SGEN_OBJECT_IS_MARKED(obj)) return; SGEN_OBJECT_SET_MARKED(obj); // 在Copying模式下对象将被复制 // 先标记复制时设置forwarding指针 sgen_mark_stack_push(sgen_gc.mark_stack, obj); }并行标记工作线程结构// sgen-mark-par.c typedef struct _SgenMarkWorker { int worker_id; SgenThreadInfo *thread_info; // 本地标记栈工作窃取 SgenMarkStack *local_stack; // 全局共享标记栈 SgenMarkStack *global_stack; // 窃取计数 gint64 steal_count; gint64 work_count; // 状态 volatile gint32 state; // IDLE, WORKING, STEALING, DONE } SgenMarkWorker; static SgenMarkWorker *mark_workers; static int mark_worker_count;并行标记主循环// sgen-mark-par.c static void sgen_mark_parallel (void) { // 初始化工作线程 for (int i 0; i mark_worker_count; i) { mark_workers[i].state WORKER_IDLE; mark_workers[i].local_stack sgen_mark_stack_new( SGEN_MARK_STACK_INIT_SIZE); } // 将根集合标记的对象分发到各工作线程 sgen_distribute_mark_work(); // 启动工作线程 for (int i 1; i mark_worker_count; i) { mono_thread_start(mark_worker_main, mark_workers[i]); } // 主线程也作为worker 0 mark_worker_main(mark_workers[0]); // 等待所有工作线程完成 sgen_wait_all_workers_done(); // 合并统计 for (int i 0; i mark_worker_count; i) { sgen_gc.stats.parallel_work_count mark_workers[i].work_count; sgen_gc.stats.parallel_steal_count mark_workers[i].steal_count; } }工作线程主循环// sgen-mark-par.c static void mark_worker_main (SgenMarkWorker *worker) { SgenMarkStack *local worker-local_stack; SgenMarkStack *global worker-global_stack; gpointer batch[SGEN_MARK_BATCH_SIZE]; while (TRUE) { // 1. 优先处理本地栈 int n sgen_mark_stack_pop_batch(local, batch, SGEN_MARK_BATCH_SIZE); if (n 0) { for (int i 0; i n; i) { sgen_mark_scan_object(batch[i]); } worker-work_count n; continue; } // 2. 本地栈空尝试从全局栈窃取 n sgen_mark_stack_steal_batch(global, batch, SGEN_MARK_BATCH_SIZE); if (n 0) { for (int i 0; i n; i) { sgen_mark_scan_object(batch[i]); } worker-steal_count n; continue; } // 3. 全局栈也空检查是否所有worker都完成 if (sgen_all_workers_idle()) { break; } // 4. 短暂等待其他worker可能还在产生工作 sgen_worker_yield(worker); } worker-state WORKER_DONE; }工作窃取Work Stealing// sgen-mark-par.c int sgen_mark_stack_steal_batch (SgenMarkStack *global, gpointer *batch, int count) { mono_mutex_lock(global-lock); if (global-size 0) { mono_mutex_unlock(global-lock); return 0; } // 窃取一半的工作平衡负载 int steal MIN(count, global-size / 2); if (steal 0) steal 1; // 至少窃取1个 for (int i 0; i steal; i) { global-top--; batch[i] *global-top; } global-size - steal; mono_mutex_unlock(global-lock); return steal; } // 当本地栈溢出时将部分工作推回全局栈 void sgen_mark_stack_overflow_to_global (SgenMarkWorker *worker) { SgenMarkStack *local worker-local_stack; // 将本地栈的一半推入全局栈 int half local-size / 2; mono_mutex_lock(worker-global_stack-lock); for (int i 0; i half; i) { local-top--; sgen_mark_stack_push(worker-global_stack, *local-top); } local-size - half; mono_mutex_unlock(worker-global_stack-lock); }Remembered Set扫描Minor GC中的Remembered Set// sgen-remembered-set.c void sgen_mark_scan_remset (void) { SgenRememberedSet *remset sgen_gc.remset; // 遍历卡片表找脏卡片 for (size_t i 0; i remset-card_table_size; i) { if (remset-card_table[i] CARD_DIRTY) { // 计算卡片对应的内存范围 char *card_start sgen_gc.nursery_start i * remset-card_size; char *card_end card_start remset-card_size; // 扫描卡片内的所有对象 sgen_scan_card(card_start, card_end); // 清除脏标记 remset-card_table[i] CARD_CLEAN; } } } static void sgen_scan_card (char *card_start, char *card_end) { // 卡片可能包含多个对象需要遍历 char *p card_start; while (p card_end) { gpointer obj sgen_object_start_at(p); if (!obj) break; // 扫描对象中的引用字段 // 这些字段可能指向Nursery中的对象 sgen_mark_scan_object_refs(obj); p sgen_object_size(obj); } }弱引用与Finalizer处理弱引用处理// sgen-mark.c void sgen_mark_weak_refs (void) { // 遍历所有弱引用 GPtrArray *weak_refs sgen_gc.weak_refs; for (int i 0; i weak_refs-len; i) { SgenWeakRef *wr g_ptr_array_index(weak_refs, i); if (wr-target !SGEN_OBJECT_IS_MARKED(wr-target)) { // 目标对象不可达清空弱引用 wr-target NULL; // 如果有trackResurrection放入复活队列 if (wr-track_resurrection) { g_ptr_array_add(sgen_gc.finaled_weak_refs, wr); } } } }Finalizer队列处理// sgen-mark.c void sgen_mark_finalize_objects (void) { // 扫描Finalizer队列中的对象 // 如果对象不可达移到可Finalize队列 GPtrArray *finalize_queue sgen_gc.finalize_queue; GPtrArray *new_queue g_ptr_array_new(); for (int i 0; i finalize_queue-len; i) { gpointer obj g_ptr_array_index(finalize_queue, i); if (SGEN_OBJECT_IS_MARKED(obj)) { // 对象存活留在队列中 g_ptr_array_add(new_queue, obj); } else { // 对象不可达加入待Finalize列表 g_ptr_array_add(sgen_gc.to_finalize, obj); } } // 替换队列 g_ptr_array_free(sgen_gc.finalize_queue, TRUE); sgen_gc.finalize_queue new_queue; }标记阶段性能优化标记位缓存// sgen-mark.c // 利用vtable低位作为标记位避免额外位图 // 但需要确保对象8字节对齐 // 伪代码标记位的缓存效果 // 对象A (vtable0x1000): 标记后 vtable0x1001 // 对象B (vtable0x2000): 标记后 vtable0x2001 // 检查时: vtable 1 1 - 已标记 // 获取真实vtable: vtable ~7批量标记优化// sgen-mark.c // 批量处理mark stack减少函数调用开销 static void sgen_mark_process_batch (gpointer *batch, int count) { for (int i 0; i count; i) { sgen_mark_scan_object(batch[i]); } }总结SGen标记阶段的核心流程sgen_mark_phase() | -- sgen_mark_scan_roots() | |-- 扫描静态字段 | |-- 扫描线程栈 | |-- 扫描GC Handle | |-- 扫描Finalizer队列 | -- 每个根 - sgen_mark_object() - push to mark stack | -- sgen_mark_scan_remset() (Minor GC only) | -- 扫描脏卡片中的引用 | -- sgen_mark_serial() / sgen_mark_parallel() | | | -- while (mark_stack not empty) | |-- pop object (灰色 - 处理中) | |-- sgen_mark_scan_object() | | |-- DESC_TYPE_SMALL: bitmap扫描 | | |-- DESC_TYPE_LARGE: 多字bitmap | | |-- DESC_TYPE_COMPLEX: 回调函数 | | |-- DESC_TYPE_VECTOR: 数组扫描 | | -- 每个引用 - sgen_mark_object() - push | -- 对象变为黑色 | -- sgen_mark_weak_refs() | -- 清除指向不可达对象的弱引用 | -- sgen_mark_finalize_objects() -- 不可达的含Finalizer对象移入待Finalize列表优化技术说明vtable低位标记避免额外标记位图利用缓存局部性Bitmap描述符O(1)定位引用字段使用BSF指令加速批量pop减少函数调用和锁竞争工作窃取并行标记的负载均衡卡片表Remembered Set的紧凑表示下一章将分析标记完成后的清扫与压缩阶段。