gcc怎么使用的(聊一聊C)
写这一篇的目的主要是因为.NET领域内几本关于阐述GC方面的书,都是纯理论,所以懂得人自然懂,不懂得人也没法亲自验证,这一篇我就用 windbg 源码
让大家眼见为实。
1. 后台GC到底解决了什么问题
解决什么问题得先说有什么问题,我们知道 阻塞版GC
有一个显著得特点就是,在 GC 触发期间,所有的用户线程都被暂停
了,这里的 暂停 是一个统称,画图如下:
这种 STW(Stop The World) 模式相信大家都习以为常了,但这里有一个很大的问题,不管当前 GC 是临时代还是全量,还是压缩或者标记,all in 全冻结,这种简单粗暴的做法肯定是不可取的,也是 后台GC
引入的先决条件。
那 后台GC 到底解决了什么问题?
解决在 FullGC 模式下的
标记清除
回收期间,放飞用户线程。
虽然这是一个很好的 Idea,但复杂度绝对上了几个档次。
三:后台GC 详解1. 后台 GC代码 骨架图
源码面前,了无秘密,在coreclr 项目的 garbage-collection.md
文件中,描述了 后台GC 的代码流程图。
GarbageCollectGeneration{SuspendEE;garbage_collect;RestartEE;}garbage_collect{generation_to_condemn;// decide to do a background GC// wake up the background GC thread to do the workdo_background_gc;}do_background_gc{init_background_gc;start_c_gc ;//wait until restarted by the BGC.wait_to_proceed;}bgc_thread_function{while (1){// wait on an event// wake upgc1;}}gc1{background_mark_phase;background_sweep;}
可以清楚的看到就是在做 标记清除
且核心逻辑都在background_mark_phase
函数中,实现了标记的三个阶段:1.初始标记
,2.并发标记
,3.最终标记
, 其中 并发标记 阶段,用户线程是正常运行的,实现了将原来整个暂停 优化到了 2个小暂停。
2. 流程图分析
为了方便说明,将三阶段画个图如下:
特别声明:阶段2的重启是在
background_sweep
方法中,而不是最终标记(background_mark_phase)
阶段。
-
初始标记
这个阶段用户线程处于暂停状态,bgc 要做的事情就是从 线程栈
和终结器队列
中寻找用户根实现引用图遍历,然后再让所有用户线程启动,简化后的代码如下:
voidgc_heap::background_mark_phase{dprintf(3, ("BGC: stack marking"));GCScan::GcScanRoots(background_promote_callback,max_generation, max_generation,&sc);dprintf(3, ("BGC: finalization marking"));finalize_queue->GcScanRoots(background_promote_callback, heap_number, 0);restart_vm;}
接下来怎么验证 阶段1
是暂停状态呢?为了方便讲述,先上一段测试代码:
internal class Program{static List<string> list = new List<string>;static void Main(string[] args){Debugger.Break;for (int i = 0; i < int.MaxValue; i ){list.Add(String.Join(",", Enumerable.Range(0, 100)));if (i % 10 == 0) list.RemoveAt(0);}}}
然后用 windbg 在 background_mark_phase 函数下一个断点:bp coreclr!WKS::gc_heap::background_mark_phase
即可。
0:009> bp coreclr!WKS::gc_heap::background_mark_phase0:009> gBreakpoint 1 hitcoreclr!WKS::gc_heap::background_mark_phase:00007ff9`e7bf73f4 488bc4 mov rax,rsp0:008> !t -specialLockDBG ID OSID ThreadOBJ State GC Mode GC Alloc Context Domain Count Apt Exception0 1 55d8 00000000006336B0 2a020 preemptive 0000000000000000:0000000000000000 000000000062d650 -00001 MTA (GC)6 2 568c 0000000000662F40 21220 Preemptive 0000000000000000:0000000000000000 000000000062d650 -00001 Ukn (Finalizer)8 4 5730 0000000000676A90 21220 Preemptive 0000000000000000:0000000000000000 000000000062d650 -00001 UknOSID Special thread type0 55d8 SuspendEE5 5688 DbgHelper6 568c Finalizer8 5730 GC
可以清楚的看到,0号线程显示了 SuspendEE字样,表示此时所有托管线程处于冻结状态。
-
并发标记
这个阶段就是各玩各的,用户线程在正常执行,bgc在后台进一步标记,因为是并行,所以存在 bgc 已标记好的对象引用关系被 用户线程
破坏,所以 bgc 用reset_write_watch
函数借助 windows 的内存页监控,目的就是把那些脏页找出来,在下一个阶段来修正,简化后的代码如下:
voidgc_heap::background_mark_phase{disable_preemptive(true);//脏页监控reset_write_watch(TRUE);revisit_written_pages(TRUE, TRUE);dprintf(3, ("BGC: handle table marking"));GCScan::GcScanHandles(background_promote,max_generation, max_generation,&sc);disable_preemptive(false);}
要想验证此时的用户线程
是放飞的,可以在revisit_written_pages
函数下一个断点即可,使用命令:bp coreclr!WKS::gc_heap::revisit_written_pages
。
0:008> !t -specialLockDBG ID OSID ThreadOBJ State GC Mode GC Alloc Context Domain Count Apt Exception0 1 55d8 00000000006336B0 2a020 Cooperative 000000000D1FD920:000000000D1FE120 000000000062d650 -00001 MTA6 2 568c 0000000000662F40 21220 Preemptive 0000000000000000:0000000000000000 000000000062d650 -00001 Ukn (Finalizer)8 4 5730 0000000000676A90 21220 Cooperative 0000000000000000:0000000000000000 000000000062d650 -00001 UknOSID Special thread type5 5688 DbgHelper6 568c Finalizer8 5730 GC
看到没有,那个 SuspendEE
神奇的消失了,而且 0 号线程的 GC 模式也改成了Cooperative
,表示可允许操控 托管堆。
-
最终标记
等 bgc 在后台做的差不多了,就可以再来一次 SupendEE
,将并发标记
期间由用户线程造成的脏引用进行最终一次修正,修正的数据来源就是监控到的Windows脏页
,代码就不上了,我们聊下怎么去验证阶段二又回到了 SuspendEE 状态?可以在background_sweep
函数下一个断点, 命令:bp coreclr!WKS::gc_heap::background_sweep
。
0:000> bp coreclr!WKS::gc_heap::background_sweep0:000> gcoreclr!WKS::gc_heap::background_sweep:00007ff9`e7b7a2e0 4053 push rbx0:008> !t -specialLockDBG ID OSID ThreadOBJ State GC Mode GC Alloc Context Domain Count Apt Exception0 1 55d8 00000000006336B0 2a020 Preemptive 0000000000000000:0000000000000000 000000000062d650 -00001 MTA6 2 568c 0000000000662F40 21220 Preemptive 0000000000000000:0000000000000000 000000000062d650 -00001 Ukn (Finalizer)8 4 5730 0000000000676A90 21220 Preemptive 0000000000000000:0000000000000000 000000000062d650 -00001 Ukn (GC)OSID Special thread type5 5688 DbgHelper6 568c Finalizer8 5730 GC SuspendEE
哈哈,可以看到那个 SuspendEE
又回来了。
3. 后台GC 只会在 fullGC 模式下吗?
这是最后一个要让大家眼见为实的问题,在gc触发期间,内部会维护一个 gc_mechanisms
结构体,其中就记录了当前 GC 触发的种种信息,可以用 windbg 把它导出来看看便知。
0:008> x coreclr!*settings*00007ff9`e7f82e90 coreclr!WKS::gc_heap::settings = class WKS::gc_mechanisms0:008> dt coreclr!WKS::gc_heap::settings 00007ff9`e7f82e90 0x000 gc_index : 0xb3 0x008 condemned_generation : 0n2 0x00c promotion : 0n1 0x010 compaction : 0n0 0x014 loh_compaction : 0n0 0x018 heap_expansion : 0n0 0x01c concurrent : 1 0x020 demotion : 0n0 0x024 card_bundles : 0n1 0x028 gen0_reduction_count : 0n0 0x02c should_lock_elevation : 0n0 0x030 elevation_locked_count : 0n0 0x034 elevation_reduced : 0n0 0x038 minimal_gc : 0n0 0x03c reason : 0 ( reason_alloc_soh ) 0x040 pause_mode : 1 ( pause_interactive ) 0x044 found_finalizers : 0n1 0x048 background_p : 0n0 0x04c b_state : 0 ( bgc_not_in_process ) 0x050 allocations_allowed : 0n1 0x054 stress_induced : 0n0 0x058 entry_memory_load : 0x49 0x060 entry_available_physical_mem : 0x00000001`0a50d000 0x068 exit_memory_load : 0
从 condemned_generation=2
可知当前触发的是 2 代GC,原因是代满了reason : 0 ( reason_alloc_soh )
。
看的再多还不如实操一遍,如果觉得手工编译 coreclr 源码麻烦,可以考虑下 windbg,好了,本篇就聊这么多,希望对你有帮助。
,免责声明:本文仅代表文章作者的个人观点,与本站无关。其原创性、真实性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容文字的真实性、完整性和原创性本站不作任何保证或承诺,请读者仅作参考,并自行核实相关内容。文章投诉邮箱:anhduc.ph@yahoo.com