简介
终于来到了gc的最后一个步骤,在此之间,大量预备工作已经完成。万事俱备,只欠东风
清除
如果gc决定不压缩,它将仅执行清除操作。清除操作非常简单,把所有不可到达对象(gap),转换成free。也就是转换成空闲内存空间。由于所有的繁重计算任务在plan_phase阶段均已完成,所以步骤比较简单
基于gap的size创建空闲列表free > 2 * min_obj_size 的free块会被放入空闲列表,小于此大小的不再被利用,但会纳入内存碎片统计
恢复“被销毁”的前置plug和plug这是pinned 对象的特殊情况,pinned的plug前面可能还是一个plug,所以没有gap来存放, 因此会根据实际情况“钉住”它的前面或者后面的plug.来暂存gap_reloc_pair信息。 所以用完后还要“还回去”
更新终结队列,并提升或降低plug的代
更新段空间
眼见为实
压缩
如果gc决定压缩,就比较复杂了。总体分为两步
复制对象并移动到新位置(重定位阶段)将新对象的地址在root上更新gc重定位阶段
此步骤更新所有对稍后要移动对象的引用,为了更新这些地址,要扫描他们的root,并逐一更新
栈空间的root跨代记忆集的root托管堆中的root前置plug与后置plug的root终结器队列的root句柄表的root
比如某个对象的内存地址为0x1000,压缩后它的新地址为0x500。那就就要对该对象的所有root更新内存地址。
眼见为实
internal class program { static void main(string[] args) { append(); appendstatic(); compact(); } public static person person; public static list<byte[]> list = new list<byte[]>(); static void append() { //填 10m 数组到 临时段上 for (int i = 0; i < 1024 * 10; i++) { list.add(new byte[1000]); } console.writeline("1. 10m 数据已分配完毕,请查看临时段大小,准备分配 person 对象!"); debugger.break(); } static void appendstatic() { person = new person(); list = null; console.writeline("2. person 已分配,list已去根,请再次观察托管堆!准备触发 gc,请下 compact_phase 断点!"); debugger.break(); } static void compact() { gc.collect(2, gccollectionmode.forced, true, true); console.writeline("3. gc 已触发,请观察 person 是否已变!"); debugger.break(); } } public class person { }
在bp coreclr!wks::gc_heap::compact_phase 下断点,观察对象的新老地址变化gc前:
gc后:内存地址发生变化
眼见为实
压缩对象
在上面更新root的操作完成后,gc要移动所有对象。由以下几个步骤组成
复制对象恢复“被销毁”的前置plug和plug重新划分代边界释放内存段创建空闲列表眼见为实
gc前:
gc后:对象被移动,原有地址被压缩释放
眼见为实:复制连续的内存区域
以滑动的方式来copy内存,避免出现覆盖问题
到此这篇关于.net core gc压缩(compact_phase)底层原理浅谈的文章就介绍到这了,更多相关.net core gc压缩(compact_phase)底层原理浅谈内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论