欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

C#內(nèi)存管理CLR深入講解(下篇)

 更新時間:2022年01月21日 11:03:41   作者:Artech  
本文詳細講解了C#內(nèi)存管理CLR的內(nèi)存分配和對大對象回收,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧

上篇》中我們主要討論的是程序集(Assembly)和應用程序域(AppDomain)的話題,著重介紹了兩個不同的程序集加載方式——獨占方式和共享方式(中立域方式);以及基于進程范圍內(nèi)的字符串駐留。這篇將關(guān)注點放在托管對象創(chuàng)建時內(nèi)存的分配和對大對象(LO:Large Object)的回收上,不對之處,還望各位能夠及時指出。

一、從類型(Type)與實例(Instance)談起

在面向?qū)ο蟮氖澜缰?,類型和實例是兩個核心的要素。不論是類型和實例,相關(guān)的信息比如加載到內(nèi)存中,對應著某一塊或者多塊連續(xù)或者不連續(xù)的內(nèi)存。那么對類型和實例的內(nèi)存分配時如何進行的呢?對象是“狀態(tài)”和“行為”的組合體,所以從.NET Framework的角度來看類型,它只具有兩種類型的成員——字段和方法(實際還有嵌套類型),前者表示狀態(tài),后者表示行為。類型是對元數(shù)據(jù)的描述,而實例則是符合該元數(shù)據(jù)描述的單個個體。同一個類型下的所有實例具有相同的行為,它們通過狀態(tài)值的不同得以區(qū)分。所以內(nèi)存中的實例(本篇所說的實例指代引用類型的實例)表示的是字段值,而內(nèi)存中的類型表示的則是類型成員結(jié)構(gòu)的元數(shù)據(jù)。很多人都知道,當我們創(chuàng)建一個對象的時候,CLR會在GC堆(Heap)中開辟一塊連續(xù)的內(nèi)存空間保存字段值。那么類型信息又是保存在那塊內(nèi)存上呢?

實際上,類型信息保存在“另一堆”上,我們稱之為加載器堆(Loader Heap)。每一個應用程序域都具有各自的加載器堆,即包括我們創(chuàng)建的普通應用程序域,也包括《上篇》中提到的三個特殊應用程序域:系統(tǒng)程序域、共享程序域和默認程序域。如果說GC堆是實例的容器,那么基于應用程序域的加載器堆就是類型的容器。CLR采用“按需加載(這里指的是類型,不是程序集)、及時編譯”的運行機制。當某個類型被第一次使用的時候,CLR試圖加載該類型。如果該類型對應的程序沒有獨自地加載到本應用程序域中,或者沒有通過中立域的形式加載到共享程序域中,它會按照相應的方式加載程序集(在這里我們假設(shè)采用獨占方式加載)。然后,將使用到的這個類型加載到本應用程序域的加載器堆中。

加載器堆維護著自應用程序域創(chuàng)建以來使用過的所有類型記錄,它們對應著一個特殊的對象——方法表(Method Table)。當程序第一次執(zhí)行到某個方法的時候,CLR會定位到方法表中該條目,獲取相關(guān)信息進行JIT編譯。所以如果某個類型在加載器堆中的方法表的某個條目至少被執(zhí)行一次,它就會指向一段JIT編譯后的機器指令。

二、實例內(nèi)存分配不僅限于GC堆

到現(xiàn)在為止,我們知道了類型和實例分別分配于基于應用程序域的加載器堆和GC堆中,那么CLR的內(nèi)存分配僅僅限于這“兩堆”嗎?當然不是,除了這“兩堆”以及默認的進程堆,還有額外“兩堆”,一是存放JIT編譯后機器指令的JIT堆(JIT Heap),另一個則是專門用于“大對象”的大對象堆(LOH: Large Object Heap)。下圖反映了CLR主要維護的這些個不同的“堆”。

對于大對象堆,在本文后續(xù)部分還會講述,在這里我們需要先了解CLR認為怎樣的對象是“大對象”。當我們實例化一個對象的時候,如果該對象大于或者等于85,000字節(jié)(這種對象一般是數(shù)組,一般對象不會這么大),CLR將認為是“大對象”并被放到LOH中,否則放到GC堆中。這里有一點需要讀者注意的是,作為垃圾回收器的GC并不僅僅限于針對GC堆中對象的回收,LOH中的對象的回收工作通過在GC的管轄之下。所以從某種意義上講:你可以將之前提到的GC堆理解為SOH(Small Object Heap),或者稱之為“狹義GC堆”,而將“廣義GC堆”理解為SOH+LOH。

三、實例對類型的引用

實例是類型的實例,實例和它所對應的類型需要維持一種聯(lián)系。反映在內(nèi)存中,就以為著分配在GC堆或者是LOH中的對象具有一個對位于加載器堆中該類型的方法表的引用。實例對類型的引用通過一個特殊的對象來維系——TypeHandle。我們舉個例子,在如下一段簡單的對象實例化代碼中 ,我先后實例化了四個對象:字符串“ABC”、System.Object對象、自定義Bar對象和具有85000個元素的字節(jié)數(shù)組。

string strInstance         = "ABC";
object objectInstance      = new object();
Bar barInstance            = new Bar()
byte[] largeObjInstance    = new byte[85000];

當上面的程序執(zhí)行后,圍繞著實例化的四個對象和類型信息,在內(nèi)存中將會具有如下一個關(guān)系。最左邊的是現(xiàn)成調(diào)用棧中的上述四個變量,對于字符串類型的strInstance,由于《上篇》所講述的關(guān)于字符串駐留機制,最后總的字符串被分配到系統(tǒng)程序域中;Object和Bar類型的objectInstance與barInstance由于是小于85000字節(jié)的小對象,所以被分配到GC堆中。objectInstance通過TypeHandle指向位于共享程序域中System.Objhect類型對應的方法表(因為定義該類型的mscorlib程序集以中立域的方式加載),而barInstance得TypeHandle指向的基于Bar類型的方法表則位于默認程序域中(因為程序域默認采用獨占的方式加載)。元素個數(shù)為85000的字節(jié)數(shù)組largeObjInstance屬于大對象,直接分配到LOH中。largeObjInstance的TypeHandle指向的基于System.Byte[]類型的方法表,該System.Byte[]類型同樣定義在mscorlib程序集中,所以該方法表同樣存在于共享程序域的加載器堆。

四、LOH中的對象如何被回收

了解GC的讀者應該都知道CLR采用基于“代齡(Generation)”的垃圾回收機制。代齡,個人覺得是一個很準確的詞語,它充分體現(xiàn)了設(shè)計者用于表現(xiàn)“不同的對象具有不同生命周期”的意思。所有對象分三代,即G0、G1和G2,這實際上代表了三個不同的連續(xù)的內(nèi)存塊。“輩分”越高,表明時間越久;“輩分”越低,被掃蕩(GC回收)的頻率就越高。關(guān)于基于代齡的垃圾回收機制,限于篇幅,就說到這里。我們的重點是GC采用怎樣的機制對LOH的對象進行回收。

到目前為止,對于LOH和GC堆中的對象,除了大小之外,我們好像沒有覺得它們之間有何不同。實際上,將大對象放在LOH中,目的在于對其實施特殊的回收機制。關(guān)于垃圾收回,我們應該有這樣的認知:回收的成本是和對象的大小基本成“正向”關(guān)系,對象越大,回收成本就越大。所以我們不能對大對象頻繁地實施垃圾回收,實際上CLR是將LOH對象當成最高代齡的對象。也就是說,針對LOH的回收工作是和GC堆中G2一并進行的。換句話說,當G2或者LOH的剩余空間低于某個限度,針對它們的垃圾回收便被觸發(fā)。關(guān)于LOH的垃圾回收機制,我們可以通過一個非常簡單的程序來驗證。

class Program
{
    static WeakReference SmallObjRef;
    static WeakReference LargeObjRef;
 
    static void Main(string[] args)
    {
        SetValues();
        GC.Collect(0);
        Console.WriteLine("GC.Collect(0)");
        Console.WriteLine("SmallObjRef.Target == null? {0}", SmallObjRef.Target == null);
        Console.WriteLine("LargeObjRef.Target == null? {0}\n", LargeObjRef.Target == null);
 
        GC.Collect(1);
        Console.WriteLine("GC.Collect(1)");
        Console.WriteLine("LargeObjRef.Target == null? {0}\n", LargeObjRef.Target == null);
 
        GC.Collect(2);
        Console.WriteLine("GC.Collect(2)");
        Console.WriteLine("LargeObjRef.Target == null? {0}\n", LargeObjRef.Target == null);
    }
 
    static void SetValues()
    {
        SmallObjRef = new WeakReference(new byte[84000]);
        LargeObjRef = new WeakReference(new byte[85000]);
    }    
}

輸出結(jié)果:

GC.Collect(0)
SmallObjRef.Target == null? True
LargeObjRef.Target == null? False
 
GC.Collect(1)
LargeObjRef.Target == null? False
 
GC.Collect(2)
LargeObjRef.Target == null? True

在上面的代碼中沒,我創(chuàng)建了兩個WeakReference對象,它們的Target分別被設(shè)置成byte[84000]和byte[85000]。按照我們上面關(guān)于對“大對象”的界定,后者是大對象,前者不是。然后,我們先后三次對G0、G1和G2實施垃圾回收,我們發(fā)現(xiàn)“小對象”在實施針對G0的垃圾回收后就沒了;而“大對象”會一直存活直到針對G2的垃圾回收被執(zhí)行。

關(guān)于CLR內(nèi)存管理一些深層次的討論[上篇]

關(guān)于CLR內(nèi)存管理一些深層次的討論[下篇]

到此這篇關(guān)于C#內(nèi)存管理CLR深入講解(下篇)的文章就介紹到這了。希望對大家的學習有所幫助,也希望大家多多支持腳本之家。

相關(guān)文章

  • C#調(diào)用Oracle存儲過程方法介紹(附源碼)

    C#調(diào)用Oracle存儲過程方法介紹(附源碼)

    這篇文章介紹了C#調(diào)用Oracle存儲過程的方法,文中通過示例代碼介紹的非常詳細。對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2022-03-03
  • C#配置文件操作類分享

    C#配置文件操作類分享

    這篇文章主要分享了C#配置文件操作類,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2017-06-06
  • c# 備忘錄模式

    c# 備忘錄模式

    備忘錄模式:在不破壞封裝的前提下,捕獲一個對象的內(nèi)部狀態(tài),并在這個對象之外的地方保存這個狀態(tài),這樣以后就可將該對象恢復到原來保存的狀態(tài)了
    2012-10-10
  • C# Winform實現(xiàn)圓角無鋸齒按鈕

    C# Winform實現(xiàn)圓角無鋸齒按鈕

    這篇文章主要為大家詳細介紹了C# Winform實現(xiàn)圓角無鋸齒按鈕,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2022-07-07
  • c#基于Redis實現(xiàn)輕量級消息組件的步驟

    c#基于Redis實現(xiàn)輕量級消息組件的步驟

    這篇文章主要介紹了c#基于Redis實現(xiàn)輕量級消息組件的步驟,幫助大家更好的理解和學習使用c#進行開發(fā),感興趣的朋友可以了解下
    2021-05-05
  • C#實現(xiàn)遠程關(guān)閉和重啟計算機的示例代碼

    C#實現(xiàn)遠程關(guān)閉和重啟計算機的示例代碼

    這篇文章主要為大家詳細介紹了如何利用C#實現(xiàn)遠程關(guān)閉和重啟計算機的功能,文中的示例代碼講解詳細,對我們學習C#有一定的幫助,感興趣的小伙伴可以跟隨小編一起了解一下
    2022-12-12
  • C#實現(xiàn)的一款比較美觀的驗證碼完整實例

    C#實現(xiàn)的一款比較美觀的驗證碼完整實例

    這篇文章主要介紹了C#實現(xiàn)的一款比較美觀的驗證碼,以完整實例形式分析了C#生成驗證碼與前端調(diào)用驗證碼的實現(xiàn)技巧,需要的朋友可以參考下
    2016-06-06
  • C#子線程執(zhí)行完后通知主線程的方法

    C#子線程執(zhí)行完后通知主線程的方法

    下面小編就為大家?guī)硪黄狢#子線程執(zhí)行完后通知主線程的方法。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2016-12-12
  • C#在運行時動態(tài)創(chuàng)建類型的實現(xiàn)方法

    C#在運行時動態(tài)創(chuàng)建類型的實現(xiàn)方法

    這篇文章主要介紹了C#在運行時動態(tài)創(chuàng)建類型的實現(xiàn)方法,主要通過動態(tài)生成C#代碼再編譯成程序集來實現(xiàn)動態(tài)創(chuàng)建類型的,需要的朋友可以參考下
    2014-09-09
  • C#提示:“在證書存儲區(qū)中找不到清單簽名證書”的解決方法

    C#提示:“在證書存儲區(qū)中找不到清單簽名證書”的解決方法

    這篇文章主要介紹了C#提示:“在證書存儲區(qū)中找不到清單簽名證書”的解決方法,分析了幾種常見的解決方案供大家選擇使用,具有一定參考借鑒價值,需要的朋友可以參考下
    2015-01-01

最新評論