资讯动态

05-02-源码-Boehm GC标记阶段

发布时间:2026/10/20 10:57:52 来源:尧图企业网站定制
源码深度Boehm GC标记阶段元数据系列Unity和C#的GC剖析与对比章节05-源码深度-Boehm GC与IL2CPP编号05-02标签Boehm GC, 保守标记, Root发现, Partial Clean, 标记栈阅读时间~30分钟依赖知识05-01数据结构概述Boehm GC的标记阶段是其最核心的组件。与SGen的精确标记不同Boehm GC使用保守标记算法不依赖对象的精确类型信息而是将对象体中所有看起来像指针的word都当作潜在引用。这种设计使得Boehm GC可以与任意C/C代码集成但也带来了伪指针风险和扫描开销。本文深入分析保守标记算法的实现、根发现机制、Partial Clean优化以及标记栈管理。保守标记算法标记阶段入口// gc_mark.c // 标记阶段入口 void GC_mark_roots (void) { // 1. 清除所有标记位 GC_clear_marks_all(); // 2. 初始化标记栈 GC_mark_stack_init(); // 3. 推送根集合 GC_push_roots(); // 4. 处理标记栈 while (!GC_mark_stack_empty()) { GC_mark_one_step(); } // 5. 处理弱引用和finalizer GC_mark_finalize(); }清除所有标记位// gc_mark.c static void GC_clear_marks_all (void) { // 遍历所有堆段清除标记位图 for (int i 0; i GC_proc_instance.heap_sects_count; i) { hblk *h GC_proc_instance.heap_sects[i]; hblkhdr *hdr GC_get_header((ptr_t)h); // 清除标记位图 memset(hdr-hb_marks, 0, sizeof(hdr-hb_marks)); } }标记单个对象// gc_mark.c // 保守标记检查地址是否指向堆内对象如果是则标记 static GC_bool GC_mark_one (ptr_t addr) { // 1. 检查是否在GC堆内 hblkhdr *hdr GC_get_header(addr); if (!hdr) { return FALSE; // 不在堆内不是对象指针 } // 2. 计算堆块基址 hblk *h (hblk*)((ptr_t)addr ~(HBLKSIZE - 1)); // 3. 检查地址是否对齐到对象边界 size_t obj_size hdr-hb_obj_size; if (obj_size 0) { // 大对象整个堆块是一个对象 // 地址必须在对象起始位置 if ((ptr_t)addr ! (ptr_t)h) { return FALSE; } } else { // 检查地址是否对齐到对象大小 size_t offset (ptr_t)addr - (ptr_t)h; if (offset % obj_size ! 0) { // 不对齐可能是指向对象内部的指针 // 保守处理找到对象起始位置 addr (ptr_t)h (offset / obj_size) * obj_size; } } // 4. 检查是否已标记 if (IS_MARKED(hdr, addr)) { return FALSE; // 已标记跳过 } // 5. 设置标记位 SET_MARK(hdr, addr); // 6. 如果对象可能含指针压入标记栈 if (hdr-hb_obj_kind ! PTRFREE) { GC_mark_stack_push(addr, hdr); } return TRUE; }保守扫描对象体// gc_mark.c // 扫描对象体中的所有word保守地标记可能的引用 static void GC_scan_object_body (ptr_t obj, hblkhdr *hdr) { size_t obj_size hdr-hb_obj_size; if (obj_size 0) { // 大对象获取实际大小 obj_size GC_get_large_obj_size(obj); } // 遍历对象体中每个word ptr_t end obj obj_size; for (ptr_t p obj; p end; p sizeof(word)) { word value *(word*)p; // 保守检查是否指向堆内 if (GC_is_heap_ptr(value)) { GC_mark_one((ptr_t)value); } } }标记栈处理// gc_mark.c // 标记栈存储待扫描的对象灰色节点 typedef struct mark_stack_entry { ptr_t obj; // 对象地址 hblkhdr *hdr; // 堆块头 size_t scan_offset; // 已扫描到的偏移增量标记用 } mark_stack_entry; static mark_stack_entry *GC_mark_stack; static mark_stack_entry *GC_mark_stack_top; static mark_stack_entry *GC_mark_stack_limit; // 压入标记栈 static void GC_mark_stack_push (ptr_t obj, hblkhdr *hdr) { if (GC_mark_stack_top GC_mark_stack_limit) { GC_mark_stack_grow(); } GC_mark_stack_top-obj obj; GC_mark_stack_top-hdr hdr; GC_mark_stack_top-scan_offset 0; GC_mark_stack_top; } // 弹出并处理一个标记栈条目 static void GC_mark_one_step (void) { GC_mark_stack_top--; mark_stack_entry *entry GC_mark_stack_top; // 保守扫描对象体 GC_scan_object_body(entry-obj, entry-hdr); } // 标记栈扩容 static void GC_mark_stack_grow (void) { size_t old_size GC_mark_stack_limit - GC_mark_stack; size_t new_size old_size * 2; mark_stack_entry *new_stack (mark_stack_entry*)GC_alloc_os_memory( new_size * sizeof(mark_stack_entry), GC_PROT_READ | GC_PROT_WRITE); memcpy(new_stack, GC_mark_stack, old_size * sizeof(mark_stack_entry)); // 更新指针 GC_mark_stack_top new_stack (GC_mark_stack_top - GC_mark_stack); GC_mark_stack new_stack; GC_mark_stack_limit new_stack new_size; }Root发现机制根集合组成// gc_root.c // Boehm GC的根集合由以下部分组成 // 1. 线程栈包括寄存器 // 2. 静态数据区.data, .bss段 // 3. 通过GC_add_roots注册的自定义根 // 4. GC Handle表 void GC_push_roots (void) { // 1. 扫描所有线程的栈和寄存器 GC_push_all_thread_stacks(); // 2. 扫描静态数据区 GC_push_roots_static(); // 3. 扫描注册的自定义根 GC_push_roots_custom(); // 4. 扫描GC Handle表 GC_push_roots_handles(); }线程栈扫描// gc_pthread.c // 扫描所有线程的栈 static void GC_push_all_thread_stacks (void) { // 遍历所有注册的线程 GC_thread *thread GC_thread_list; while (thread) { // 获取线程的栈范围 ptr_t stack_bottom thread-stack_base; ptr_t stack_top thread-stack_ptr; // 当前栈指针 // 保存的寄存器上下文 ptr_t regs (ptr_t)thread-context; size_t regs_size sizeof(thread-context); // 1. 保守扫描寄存器 GC_push_all(regs, regs regs_size); // 2. 保守扫描栈 GC_push_all_stack(stack_bottom, stack_top); thread thread-next; } }保守栈扫描详解// gc_root.c // 保守扫描一段内存区域 void GC_push_all (ptr_t bottom, ptr_t top) { ptr_t p; // 遍历区域中每个word for (p bottom; p top; p sizeof(word)) { word value *(word*)p; // 快速过滤检查地址范围 // 优化先检查是否在堆段的大致范围内 if (GC_quick_is_heap_ptr(value)) { // 精确检查页表查找 if (GC_is_heap_ptr(value)) { // 标记对象 GC_mark_one((ptr_t)value); } } } } // 快速过滤使用地址范围检查 static inline GC_bool GC_quick_is_heap_ptr (word addr) { // 检查是否在堆的大致地址范围内 // 这是一个快速但不精确的检查 return (addr GC_heap_addr_min addr GC_heap_addr_max); }静态数据区扫描// gc_root.c // 扫描静态数据区.data和.bss段 static void GC_push_roots_static (void) { struct roots_static *root GC_proc_instance.static_roots; while (root) { GC_push_all(root-start, root-end); root root-next; } } // 注册静态数据区在GC初始化时调用 void GC_register_static_roots (void) { // .data段 extern char __data_start[]; extern char __data_end[]; GC_add_roots(__data_start, __data_end); // .bss段 extern char __bss_start[]; extern char __bss_end[]; GC_add_roots(__bss_start, __bss_end); }GC Handle扫描// gc_handle.c // Boehm GC支持类似.NET的GC Handle机制 // 在IL2CPP中用于实现GCHandle、WeakReference等 typedef struct { void *obj; // 指向的对象 int type; // Handle类型 // 类型: NORMAL, PINNED, WEAK, WEAK_TRACK_RESURRECTION } GC_handle_entry; static GC_handle_entry *GC_handle_table; static size_t GC_handle_table_size; static void GC_push_roots_handles (void) { for (size_t i 0; i GC_handle_table_size; i) { if (GC_handle_table[i].obj) { switch (GC_handle_table[i].type) { case GC_HANDLE_NORMAL: case GC_HANDLE_PINNED: // 强引用标记对象 GC_mark_one((ptr_t)GC_handle_table[i].obj); break; case GC_HANDLE_WEAK: case GC_HANDLE_WEAK_TRACK: // 弱引用不标记在标记完成后处理 break; } } } }Partial Clean机制什么是Partial CleanPartial Clean是Boehm GC的一种优化策略在标记阶段完成后、清扫阶段之前对某些明显不可达的堆块进行快速清理减少完整清扫的工作量。// gc_reclaim.c // Partial Clean在标记完成后立即回收某些块 void GC_partial_clean (void) { // 遍历所有堆块 for (int i 0; i GC_proc_instance.heap_sects_count; i) { hblk *h GC_proc_instance.heap_sects[i]; hblkhdr *hdr GC_get_header((ptr_t)h); // 只处理PTRFREE类型的块 // PTRFREE对象不含指针可以快速判断可达性 if (hdr-hb_obj_kind PTRFREE) { GC_partial_clean_block(h, hdr); } } } static void GC_partial_clean_block (hblk *h, hblkhdr *hdr) { size_t obj_size hdr-hb_obj_size; int obj_count HBLKSIZE / obj_size; // 检查块中每个对象的标记状态 int all_unmarked TRUE; int all_marked TRUE; for (int i 0; i obj_count; i) { ptr_t obj (ptr_t)h i * obj_size; if (IS_MARKED(hdr, obj)) { all_unmarked FALSE; } else { all_marked FALSE; // 回收未标记的对象 GC_reclaim_obj(obj, obj_size); } } if (all_unmarked) { // 整个块都不可达可以归还给OS或加入空闲块池 GC_free_hblk(h); } else if (all_marked) { // 整个块都存活跳过清扫 hdr-hb_last_gc GC_proc_instance.gc_no; } }Partial Clean的优势// gc_reclaim.c // Partial Clean减少了完整清扫的工作量 // 特别对PTRFREE对象如byte[], int[]等效果显著 // 性能对比 // 不使用Partial Clean: // 标记 - 清扫所有块 - 重建空闲链表 // 时间复杂度: O(堆大小) // // 使用Partial Clean: // 标记 - Partial Clean(PTRFREE块) - 清扫剩余块 // PTRFREE块可以快速处理无需扫描对象体 // 时间复杂度: O(非PTRFREE块大小) O(PTRFREE块数)标记阶段优化标记位图缓存优化// gc_mark.c // Boehm GC使用位图标记与SGen的vtable低位标记不同 // 位图的优势不修改对象体对缓存友好 // 劣势需要额外的内存开销 // 标记位图布局: // 堆块(4KB): // ------------------------------ // | O0| O1| O2| O3| O4| O5| O6| O7| ... | // ------------------------------ // // 标记位图(每个对象1位): // ------------------------ // | 1 | 0 | 1 | 0 | 0 | 0 | 1 | 0 | (O0存活, O2存活, O6存活) // ------------------------批量标记// gc_mark.c // 批量处理标记栈减少函数调用开销 static void GC_mark_batch (int batch_size) { mark_stack_entry batch[GC_MARK_BATCH_SIZE]; int count 0; // 批量弹出 while (count batch_size !GC_mark_stack_empty()) { GC_mark_stack_top--; batch[count] *GC_mark_stack_top; } // 批量处理 for (int i 0; i count; i) { GC_scan_object_body(batch[i].obj, batch[i].hdr); } }并行标记// gc_par_mark.c // Boehm GC支持并行标记可选 typedef struct { mark_stack_entry *local_stack; mark_stack_entry *local_stack_limit; mark_stack_entry *local_stack_top; // 全局共享标记栈用于工作窃取 mark_stack_entry *global_stack; mono_mutex_t *global_lock; int worker_id; volatile int *done_flag; } GC_mark_worker; static void GC_mark_parallel (void) { int n_workers GC_proc_instance.mark_parallelism; GC_mark_worker *workers (GC_mark_worker*)calloc(n_workers, sizeof(GC_mark_worker)); // 初始化工作线程 for (int i 0; i n_workers; i) { workers[i].worker_id i; workers[i].global_stack GC_mark_stack; workers[i].global_lock GC_mark_stack_lock; workers[i].done_flag GC_mark_done; // 分配本地标记栈 workers[i].local_stack (mark_stack_entry*)GC_alloc_os_memory( GC_MARK_STACK_INIT_SIZE * sizeof(mark_stack_entry), GC_PROT_READ | GC_PROT_WRITE); workers[i].local_stack_limit workers[i].local_stack GC_MARK_STACK_INIT_SIZE; workers[i].local_stack_top workers[i].local_stack; } // 启动工作线程 for (int i 1; i n_workers; i) { GC_start_mark_thread(workers[i]); } // 主线程也参与标记 GC_mark_worker_main(workers[0]); // 等待所有工作线程完成 GC_wait_mark_workers_done(); }弱引用与Finalizer处理弱引用处理// gc_mark.c // 标记完成后处理弱引用 void GC_mark_finalize (void) { // 1. 处理弱引用 GC_process_weak_refs(); // 2. 处理finalizer队列 GC_process_finalize_queue(); } static void GC_process_weak_refs (void) { // 遍历所有弱引用 for (size_t i 0; i GC_handle_table_size; i) { if (GC_handle_table[i].type GC_HANDLE_WEAK || GC_handle_table[i].type GC_HANDLE_WEAK_TRACK) { void *obj GC_handle_table[i].obj; if (obj !GC_is_marked(obj)) { // 目标不可达清空弱引用 GC_handle_table[i].obj NULL; if (GC_handle_table[i].type GC_HANDLE_WEAK_TRACK) { // 加入复活队列 GC_add_to_resurrection_queue(obj); } } } } }Finalizer处理// gc_mark.c static void GC_process_finalize_queue (void) { // 遍历finalizer队列 GC_finalize_entry *entry GC_finalize_list; GC_finalize_entry *prev NULL; while (entry) { GC_finalize_entry *next entry-next; if (!GC_is_marked(entry-obj)) { // 对象不可达需要finalizer // 临时标记对象使其在本次GC中存活 GC_mark_one((ptr_t)entry-obj); // 移到待执行队列 GC_add_to_to_finalize(entry-obj, entry-fn); // 从finalizer队列中移除 if (prev) prev-next next; else GC_finalize_list next; } else { prev entry; } entry next; } }保守标记的误标记分析伪指针场景// 伪指针示例 // 栈上有一个整数值 0x7F3A0010 // 恰好堆中有一个对象地址为 0x7F3A0010 // 保守GC会将这个整数误认为对象引用标记该对象为存活 // 伪代码展示 void foo() { int x 0x7F3A0010; // 一个整数值 // ... 此时GC触发 // GC扫描栈发现x的值看起来像堆地址 // GC标记地址0x7F3A0010处的对象为存活 // 即使该对象实际上不可达 }误标记的影响// 误标记导致的问题 // 1. 内存泄漏不可达对象被误标记为存活无法回收 // 2. 级联效应被误标记对象的引用也被标记 // 导致更多对象无法回收 // 3. 不可预测性误标记取决于运行时的栈/寄存器状态 // 同一程序在不同运行中可能有不同的GC行为 // 缓解措施 // 1. 黑名单机制记录可疑地址避免在这些地址分配对象 // 2. 对齐检查只将对齐到对象边界的地址视为有效引用 // 3. 堆块隔离不同类型的对象分配在不同区域 // 减少伪指针命中的概率总结Boehm GC标记阶段流程: GC_mark_roots() | -- GC_clear_marks_all() // 清除所有标记位 | -- GC_push_roots() // 推送根集合 | |-- GC_push_all_thread_stacks() // 扫描线程栈寄存器 | |-- GC_push_roots_static() // 扫描静态数据区 | |-- GC_push_roots_custom() // 扫描自定义根 | -- GC_push_roots_handles() // 扫描GC Handle | -- while (!mark_stack_empty) // 处理标记栈 | -- GC_scan_object_body() // 保守扫描对象体 | -- 遍历每个word | -- GC_is_heap_ptr() // 检查是否指向堆 | -- GC_mark_one() // 标记对象压栈 | -- GC_mark_finalize() // 处理弱引用和finalizer |-- GC_process_weak_refs() // 清空指向不可达对象的弱引用 -- GC_process_finalize_queue() // 临时标记需finalizer的对象特性保守标记精确标记(SGen)类型信息不需要需要描述符扫描开销高扫描所有word低只扫描引用字段误标记风险有伪指针无对象移动不支持支持适用场景C/C混合纯托管代码Boehm GC的保守标记策略是其最大的设计决策既带来了通用性优势也引入了性能和正确性方面的挑战。下一章将分析分配与碎片管理。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑