任我行传奇 發表於 2025-1-26 08:59:38

.NET Core GC压缩(compact_phase)底层原理解析

<div id="navCategory"><h5 class="catalogue">目录</h5><ul class="first_class_ul"><li><a href="#_label0">简介</a></li><li><a href="#_label1">清除</a></li><li><a href="#_label2">更新段空间</a></li><li><a href="#_label3">压缩</a></li><li><a href="#_label4">压缩对象</a></li></ul></div><p class="maodian"><a name="_label0"></a></p><h2>简介</h2>
<p>终于来到了GC的最后一个步骤,在此之间,大量预备工作已经完成。<code>万事俱备,只欠东风</code></p>
<p class="maodian"><a name="_label1"></a></p><h2>清除</h2>
<p>如果GC决定不压缩,它将仅执行清除操作。清除操作非常简单,把所有不可到达对象(gap),转换成Free。也就是转换成空闲内存空间。由于所有的繁重计算任务在plan_phase阶段均已完成,所以步骤比较简单</p>
<p>基于gap的size创建空闲列表free &gt; 2 * min_obj_size 的Free块会被放入空闲列表,小于此大小的不再被利用,但会纳入内存碎片统计</p>
<p>恢复&ldquo;被销毁&rdquo;的前置plug和plug这是pinned 对象的特殊情况,pinned的plug前面可能还是一个plug,所以没有gap来存放, 因此会根据实际情况&ldquo;钉住&rdquo;它的前面或者后面的Plug.来暂存gap_reloc_pair信息。 所以用完后还要&ldquo;还回去&rdquo;</p>
<p>更新终结队列,并提升或降低plug的代</p>
<p class="maodian"><a name="_label2"></a></p><h2>更新段空间</h2>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202501/202501260900401.png" /></p>
<p>眼见为实</p>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202501/202501260900402.png" /></p>
<p class="maodian"><a name="_label3"></a></p><h2>压缩</h2>
<p>如果GC决定压缩,就比较复杂了。总体分为两步</p>
<p>复制对象并移动到新位置(重定位阶段)将新对象的地址在root上更新GC重定位阶段</p>
<p>此步骤更新所有对稍后要移动对象的引用,为了更新这些地址,要扫描他们的root,并逐一更新</p>
<p>栈空间的root跨代记忆集的root托管堆中的root前置plug与后置Plug的root终结器队列的root句柄表的root</p>
<p>比如某个对象的内存地址为0x1000,压缩后它的新地址为0x500。那就就要对该对象的所有root更新内存地址。</p>
<p>眼见为实</p>
<div class="jb51code"><pre class="brush:csharp;">    internal class Program
    {
      static void Main(string[] args)
      {
            Append();
            AppendStatic();
            Compact();
      }
      public static Person person;
      public static List&lt;byte[]&gt; list = new List&lt;byte[]&gt;();
      static void Append()
      {
            //填 10M 数组到 临时段上
            for (int i = 0; i &lt; 1024 * 10; i++)
            {
                list.Add(new byte);
            }
            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 { }</pre></div>
<p>在bp coreclr!WKS::gc_heap::compact_phase 下断点,观察对象的新老地址变化GC前:</p>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202501/202501260900403.png" /></p>
<p>GC后:内存地址发生变化</p>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202501/202501260900404.png" /></p>
<p>眼见为实</p>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202501/202501260900405.png" /></p>
<p class="maodian"><a name="_label4"></a></p><h2>压缩对象</h2>
<p>在上面更新root的操作完成后,GC要移动所有对象。由以下几个步骤组成</p>
<p>复制对象恢复&ldquo;被销毁&rdquo;的前置plug和plug重新划分代边界释放内存段创建空闲列表眼见为实</p>
<p>GC前:</p>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202501/202501260900406.png" /></p>
<p>GC后:对象被移动,原有地址被压缩释放</p>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202501/202501260900407.png" /></p>
<p>眼见为实:复制连续的内存区域</p>
<p>以滑动的方式来copy内存,避免出现覆盖问题</p>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202501/202501260900408.png" /></p>
頁: [1]
查看完整版本: .NET Core GC压缩(compact_phase)底层原理解析