狠狠色丁香婷婷综合尤物/久久精品综合一区二区三区/中国有色金属学报/国产日韩欧美在线观看 - 国产一区二区三区四区五区tv

LOGO OA教程 ERP教程 模切知識交流 PMS教程 CRM教程 開發文檔 其他文檔  
 
網站管理員

.NET Core 堆結構(Heap)底層原理淺談

freeflydom
2024年12月14日 10:51 本文熱度 452

.Net托管堆布局

加載堆

主要是供CLR內部使用,作為承載程序的元數據。

  1. HighFrequencyHeap
    存放CLR高頻使用的內部數據,比如MethodTable,MethodDesc.

通過is判斷類型之間的繼承關系,調用接口的方法和虛方法,都需要訪問MethodTable

  1. LowFrequencyHeap
    存放CLR低頻使用的內部數據,比如EEClass,ClassLoader.

GC信息與異常處理表,它們都只在發生時才訪問,因此訪問頻率不高。

  1. StringLiteralMap
    字符串駐留池:https://www.cnblogs.com/lmy5215006/p/18494483

字符串對象本身存儲在FOH堆中,String Literal Map只是一個索引

  1. StubHeap
    函數入口的代碼堆
  2. CodeHeap
    JIT編譯代碼使用的內部堆,比如生成IL。
  3. VirtualCallStubHeap
    虛方法調用的內部堆

使用!eeheap -loader可以查看

眼見為實

新版sos呈現方式不一樣,可以使用老版sos展示文中所述內容

托管堆

大家的老朋友了,不做過多解釋,由GC統一管理的內存堆.一個.NET程序中所有的Domain都會共用一個托管堆

  1. SOH
    略略略
  2. LOH
    略略略
  3. POH
    固定對象專屬的堆,比如非托管線程訪問托管對象,就需要把對象固定起來,避免被GC回收造成非托管代碼的訪問違例.

使用!eeheap -gc可以查看

眼見為實

凍結堆

.NET8推出來的一個新堆,用來存放永遠不會被GC管理的永生對象,比如string 字面量。
簡單來說,就是一個對象你都永遠不會釋放了,還放在托管堆就是浪費了。不如單獨拎出來存。

眼見為實

https://www.cnblogs.com/lmy5215006/p/18515971

上述所說的各種堆,只是一個邏輯上的概念。作為內存的物理承載。由堆段(Heap Seg-ment)實現.
簡單來說,段是托管堆的物理表示。

眼見為實

segmentbeginallocatedcommitted allocated sizecommitted size
段指針的對象地址內存分配的起始點內存分配的末尾點已提交的分配大小已提交的大小

SOH小對象堆

堆只是一個抽象的概念,在物理上的表現形式為內存段,作為CLR細化堆的一種管理單位。多個段組成了堆。

.NET8之前的段結構

在.NET 8 之前,段分為SOH,LOH,POH 三個段。
對于SOH段有點特殊,因為段上面還有分代邏輯。包含0代和1代的對象只會分配在新分配的內存段上(臨時段),剩下的每個段都是2代的段

可以看到,代只是一個邏輯概念,并沒有獨立的段空間。0,1,2代共享段空間。

.NET8的段結構

到了.NET 8,代已經不是一個邏輯概念,而是一個物理概念。
每個代都有了自己獨立的段空間。

代機制

每當GC觸發時,所有對象(非固定)都會進行升代,直到gen2為止。

  1. obj對象剛創建,為0代
    內存地址為0x00000263ee009528,0x01fb08000028>0x000001fb080b71e0>01fb080b9068 說明obj放在0代里
  2. 第一次GC,obj升為1代
    內存地址在1代空間范圍內
  3. 第二次GC,obj升為2代
    內存地址在2代空間范圍內

代邊界

細心的朋友會發現一個盲點,就是obj剛剛創建的時候,0代內存起始點為0263ee000028,升為1代后,1代內存起始點也變為了0263ee000028,2代也同樣。
這就引申出另一個概念,GC升代,不是簡單的copy對象從0代到1代。而是移動代的邊界。
每次GC觸發時,代邊界指針會在多個“地址段”上遷移,通過這種邏輯操作,達到性能的最高,可以觀察上面的 Allocated 區,一會給了 0gen,一會又給了 1gen,一會又給了 2gen

LOH大對象堆

大對象堆存儲所有>=85000byte的對象,但也是有例外。LOH堆上對象管理相對寬松,沒有“代”機制,默認情況下也不會壓縮。

例外1-32位環境下的double[]

        static void Main(string[] args)
        {
            double[] array1 = new double[999];
            Console.WriteLine(GC.GetGeneration(array1));
            double[] array2 = new double[1000];
            Console.WriteLine(GC.GetGeneration(array2));
            double[,] array3 = new double[32,32];
            Console.WriteLine(GC.GetGeneration(array3));
            long[] array4 = new long[1000];
            Console.WriteLine(GC.GetGeneration(array4));
            Debugger.Break();
            Console.ReadKey();
        }

這里有個很奇怪的現象,在32位環境下,array2的大小= 4b+4+4+1000*8=8012byte. 遠遠<=85000byte. 為什么被分配到了LOH堆?
這主要跟內存對齊有關,double的未對齊訪問非常昂貴,遠遠超過long,ulong,int。這對于64位環境來說不是問題,總是對SOH與LOH使用8byte對齊。但對于4字節對齊的32位環境。這就是個大問題了.
所以CLR開發團隊決定將閾值大于1000的double放入LOH堆(LOH堆總是8byte對齊)。避免了double未對齊訪問的巨大成本

例外2-StringInter與靜態成員以及元數據

https://www.cnblogs.com/lmy5215006/p/18515971
參考此文,在.NET5之前沒有POH堆,所以CLR內部使用的三個數組也會進入LOH堆。
三個數組分別為

  1. static對象的object[]
  2. 字符串池 object[]
  3. 元數據 RuntimeType object[]

其實很好理解,這些都是低頻變化的內容,放在LOH堆上好過放在SOH堆。

POH堆

POH堆解決了什么問題?
從.NET5開始,CLR團隊給pinned的對象單獨放入一個段中,這樣pinned對象不會和普通對象混在一起。導致大量細小Free空間。從而降低托管堆碎片化,也降低了代降級的頻次。

有點遺憾的是,非托管代碼造成的對象固定,并不會移動到POH堆中。因此代降級的現象依舊存在。
感覺未來微軟可以重點優化這塊,固定對象是GC速度最大的阻礙。

如何使用POH堆?

在.NET 8中,將對象放入POH堆是一種“有意為之”行為,必須調用 GC 類提供的 AllocateArray 和 AllocateUninitializedArray 方法并設置 pinned=true

FOH

FOH堆解決了什么問題?
在.NET8中,如果一個對象在創建的時候,就明確知道是“永生”對象,那就沒必要納入托管堆的管理范圍,只會徒增GC的工作量。因此干脆把對象放在托管堆之外,來提高性能

常見的例子就是字符串的字面量(literal)

static對象布局,不會被GC回收的對象1

靜態的基元類型(short,int,long) ,它的值本身并不存放在托管堆上。而是存放在Domain中的高頻堆中

靜態的引用類型則不同。真正的對象存放在托管堆上,再由POH中一個object[]持有,最后被高頻堆中的m_pGCStatics所管理

Domain下每一個Module都維護了一個DomainLocalModule結構,靜態變量放在該Module中

眼見為實:靜態基元類型分配在高頻堆上?

    internal class Program
    {
        static long age = 10086;
        static void Main(string[] args)
        {
            age = 12;
            Console.WriteLine("done. " + age);
            Debugger.Break();
        }
    }


通過匯編得知,static a的地址為00007ff9a618e4a8

觀察高頻堆地址可以發現,00007FF9A6180000<00007ff9a618e4a8<00007FF9A6190000 。明顯屬于高頻堆

眼見為實:靜態引用類型分配在哪?

    internal class Program
    {
        public static Person person = new Person();
        static void Main(string[] args)
        {
            var num = person.age;
            Console.WriteLine(num);
            Debugger.Break();
        }
    }
    public class Person
    {
        public int age = 12;
    }
  1. 使用!gcwhere命令來查看person對象屬于0代中,說明對象本身分配在托管堆

  2. 使用!gcroot命令查看它的引用根,發現它被一個object[]所持有

  3. 再查看object[]的所屬代,可以看到該對象屬于POH堆

  4. bp coreclr!JIT_GetSharedNonGCStaticBase_Helper 下斷點來獲取 DomainLocalModule 實例

    注意,這里我重新運行了一遍,所以object[]內存地址有變

字符串駐留池布局,不會被GC回收的對象2

關于字符串的不可變性,參考此文:https://www.cnblogs.com/lmy5215006/p/18494483

在.NET8之前,字符串駐留與靜態引用類型處理模式無差別。
.NET 8加入FOH堆之后,會將編譯期間就能確定的字符串放入FOH堆,以便提高GC性能。

眼見為實

        static void Main(string[] args)
        {
            var str1 = "hello FOH";//編譯期間能確定
            var str2 = Console.ReadLine();
            string.Intern(str2);//運行期間才能確定
            Console.WriteLine($"str1={str1},str2={str2}");
            Debugger.Break();
        }
  1. 編譯期間能確定的,直接加入了FOH

  2. 運行期間確定,與靜態引用類型處理流程一致

轉自https://www.cnblogs.com/lmy5215006/p/18583743


該文章在 2024/12/14 10:51:40 編輯過
關鍵字查詢
相關文章
正在查詢...
點晴ERP是一款針對中小制造業的專業生產管理軟件系統,系統成熟度和易用性得到了國內大量中小企業的青睞。
點晴PMS碼頭管理系統主要針對港口碼頭集裝箱與散貨日常運作、調度、堆場、車隊、財務費用、相關報表等業務管理,結合碼頭的業務特點,圍繞調度、堆場作業而開發的。集技術的先進性、管理的有效性于一體,是物流碼頭及其他港口類企業的高效ERP管理信息系統。
點晴WMS倉儲管理系統提供了貨物產品管理,銷售管理,采購管理,倉儲管理,倉庫管理,保質期管理,貨位管理,庫位管理,生產管理,WMS管理系統,標簽打印,條形碼,二維碼管理,批號管理軟件。
點晴免費OA是一款軟件和通用服務都免費,不限功能、不限時間、不限用戶的免費OA協同辦公管理系統。
Copyright 2010-2025 ClickSun All Rights Reserved