午夜国产狂喷潮在线观看|国产AⅤ精品一区二区久久|中文字幕AV中文字幕|国产看片高清在线

    .NET垃圾回收:原理淺析
    來源:易賢網(wǎng) 閱讀:1676 次 日期:2015-04-02 13:09:19
    溫馨提示:易賢網(wǎng)小編為您整理了“.NET垃圾回收:原理淺析”,方便廣大網(wǎng)友查閱!

    在開發(fā).NET程序過程中,由于CLR中的垃圾回收(garbage collection)機制會管理已分配的對象,所以程序員就可以不用關(guān)注對象什么時候釋放內(nèi)存空間了。但是,了解垃圾回收機制還是很有必要的,下面我們就看看.NET垃圾回收機制的相關(guān)內(nèi)容。

    創(chuàng)建對象

    在C#中,我們可以通過new關(guān)鍵字創(chuàng)建一個引用類型的對象,比如下面一條語句。New關(guān)鍵字創(chuàng)建了一個Student類型的對象,這個新建的對象會被存放在托管堆中,而這個對象的引用會存放在調(diào)用棧中。(對于引用類型可以查看,C#中值類型和引用類型)

    Student s1 = new Student();

    在C#中,當上面的Student對象被創(chuàng)建后,程序員就可以不用關(guān)心這個對象什么時候被銷毀了,垃圾回收器將會在該對象不再需要時將其銷毀。

    當一個進程初始化后,CLR就保留一塊連續(xù)的內(nèi)存空間,這段連續(xù)的內(nèi)存空間就是我們說的托管堆。.NET垃圾回收器會管理并清理托管堆,它會在必要的時候壓縮空的內(nèi)存塊來實現(xiàn)優(yōu)化,為了輔助垃圾回收器的這一行為,托管堆保存著一個指針,這個指針準確地只是下一個對象將被分配的位置,被稱為下一個對象的指針(NextObjPtr)。為了下面介紹垃圾回收機制,我們先詳細看看new關(guān)鍵字都做了什么。

    new關(guān)鍵字

    當C#編譯器遇到new關(guān)鍵字時,它會在方法的實現(xiàn)中加入一條CIL newobj命令,下面是通過ILSpy看到的IL代碼。

    IL_0001: newobj instance void GCTest.Student::.ctor()

    其實,newobj指令就是告訴CLR去執(zhí)行下列操作:

    計算新建對象所需要的內(nèi)存總數(shù)

    檢查托管堆,確保有足夠的空間來存放新建的對象

    如果空間足夠,調(diào)用類型的構(gòu)造函數(shù),將對象存放在NextObjPtr指向的內(nèi)存地址

    如果空間不夠,就會執(zhí)行一次垃圾回收來清理托管堆(如果空間依然不夠,就會報出OutofMemoryException)

    最后,移動NextObjPtr指向托管堆下一個可用地址,然后將對象引用返回給調(diào)用者

    按照上面的分析,當我們創(chuàng)建兩個Student對象的時候,托管堆就應(yīng)該跟下圖一致,NextObjPtr指向托管堆新的可用地址。

    托管堆的大小不是無限制的,如果我們一直使用new關(guān)鍵字來創(chuàng)建新的對象,托管堆就可能被耗盡,這時托管堆可以檢測到NextObjPtr指向的空間超過了托管堆的地址空間,就需要做一次垃圾回收了,垃圾回收器會從托管堆中刪除不可訪問的對象

    應(yīng)用程序的根

    垃圾回收器是如何確定一個對象不再需要,可以被安全的銷毀?

    這里就要看一個應(yīng)用程序根(application root)的概念。根(root)就是一個存儲位置其中保存著對托管堆上一個對象的引用,根可以屬性下面任何一個類別:

    全局對象和靜態(tài)對象的引用

    應(yīng)用程序代碼庫中局部對象的引用

    傳遞進一個方法的對象參數(shù)的引用

    等待被終結(jié)(finalize,后面介紹)對象的引用

    任何引用對象的CPU寄存器

    垃圾回收可以分為兩個步驟:

    標記對象

    壓縮托管堆

    下面結(jié)合應(yīng)用程序的根的概念,我們來看看垃圾回收這兩個步驟。

    標記對象

    在垃圾回收的過程中,垃圾回收器會認為托管堆中的所有對象都是垃圾,然后垃圾回收器會檢查所有的根。為此,CLR會建立一個對象圖,代表托管堆上所有可達對象。

    假設(shè)托管堆中有A-G七個對象,垃圾回收過程中垃圾回收器會檢查所有的對象是否有活動根。這個例子的垃圾回收過程可以描述如下(灰色表示不可達對象):

    當發(fā)現(xiàn)有根引用了托管堆中的對象A時,垃圾回收器會對此對象A進行標記

    對一個根檢測完畢后會接著檢測下一個根,執(zhí)行步驟一種同樣的標記過程,標記對象B,在標記B時,檢測到對象B內(nèi)又引用了另一個對象E,則也對E進行標記;由于E引用了G,同樣的方式G也會被標記

    重復步驟二,檢測Globales根,這次標記對象D

    代碼中很有可能多個對象中引用了同一個對象E,垃圾回收器只要檢測到對象E已經(jīng)被標記過,則不再對對象E內(nèi)所引用的對象進行檢測,這樣做有兩個目的:一是提高性能,二是避免無限循環(huán)。

    所有的根對象都檢查完之后,有標記的對象就是可達對象,未標記的對象就是不可達對象。

    壓縮托管堆

    繼續(xù)上面的例子,垃圾回收器將銷毀所有未被標記的對象,釋放這些垃圾對象所占的內(nèi)存,再把可達對象移動到這里以壓縮堆。

    注意,在移動可達對象之后,所有引用這些對象的變量將無效,接著垃圾回收器要重新遍歷應(yīng)用程序的所有根來修改它們的引用。在這個過程中如果各個線程正在執(zhí)行,很可能導致變量引用到無效的對象地址,所以整個進程的正在執(zhí)行托管代碼的線程是被掛起的。

    經(jīng)過了垃圾回收之后,所有的非垃圾對象被移動到一起,并且所有的非垃圾對象的指針也被修改成移動后的內(nèi)存地址,NextObjPtr指向最后一個非垃圾對象的后面。

    對象的代

    當CLR試圖尋找不可達對象的時候,它需要遍歷托管堆上的對象。隨著程序的持續(xù)運行,托管堆可能越來越大,如果要對整個托管堆進行垃圾回收,勢必會嚴重影響性能。所以,為了優(yōu)化這個過程,CLR中使用了”代”的概念,托管堆上的每一個對象都被指定屬于某個”代”(generation)。

    “代”這個概念的基本思想就是,一個對象在托管堆上存在的時間越長,那么它就更可能應(yīng)該保留。托管堆中的對象可以被分為0、1、2三個代:

    0代:從沒有被標記為回收的新分配的對象

    1代:在上一次垃圾回收中沒有被回收的對象

    2代:在一次以上的垃圾回收后仍然沒有被回收的對象

    下面還是通過一個例子看看代這個概念(灰色代表不可達對象):

    在程序初始化時,托管堆上沒有對象,這時候新添到托管堆上的對象是的代是0,這些對象從來沒有經(jīng)過垃圾回收器檢查。假設(shè)現(xiàn)在托管堆上有A-G七個對象,托管堆空間將要耗盡。

    如果現(xiàn)在需要更多的托管堆空間來存放新建的對象(H、I、J),CLR就會觸發(fā)一次垃圾回收。垃圾回收器就會檢查所有的0代對象,所有的不可達對象都會被清理,所有沒有被回收掉的對象就成為了1代對象。

    假設(shè)現(xiàn)在需要更多的托管堆空間來存放新建的對象(K、L、M),CLR會再觸發(fā)一次垃圾回收。垃圾回收器會先檢查所有的0代對象,但是仍需要更多的空間,那么垃圾回收器會繼續(xù)檢查所有 的1代對象,整理出足夠的空間。這時,沒有被回收的1代對象將成為2代對象。2代對象是目前垃圾回收器的最高代,當再次垃圾回收時,沒有回收的對象的代數(shù)依然保持2。

    通過前面的描述可以看到,分代可以避免每次垃圾回收都遍歷整個托管堆,這樣可以提高垃圾回收的性能。

    System.GC

    .NET類庫中提供了System.GC類型,通過該類型的一些靜態(tài)方法,可以通過編程的方式與垃圾回收器進行交互。

    看一個簡單的例子:

    class Student

    {

    public int Id { get; set; }

    public string Name { get; set; }

    public int Age { get; set; }

    public string Gender { get; set; }

    }

    class Program

    {

    static void Main(string[] args)

    {

    Console.WriteLine("Estimated bytes on heap: {0}", GC.GetTotalMemory(false));

    Console.WriteLine("This OS has {0} object generations", GC.MaxGeneration);

    Student s = new Student { Id = 1, Name = "Will", Age = 28, Gender = "Male"};

    Console.WriteLine(s.ToString());

    Console.WriteLine("Generation of s is: {0}", GC.GetGeneration(s));

    GC.Collect();

    Console.WriteLine("Generation of s is: {0}", GC.GetGeneration(s));

    GC.Collect();

    Console.WriteLine("Generation of s is: {0}", GC.GetGeneration(s));

    Console.Read();

    }

    }

    程序的輸出為:

    從這個輸出,我們也可以驗證代的概念,每次垃圾清理后,如果一個對象沒有被清理,那么它的代就會提高。

    強制垃圾回收

    由于托管堆上的對象由垃圾管理器幫我們管理,所有我們不需要關(guān)心托管堆上對象的銷毀以及內(nèi)存空間的回收。

    但是,有些特殊的情況下,我們可能需要通過GC.Collect()強制垃圾回收:

    應(yīng)用程序?qū)⒁M入一段代碼,這段代碼不希望被可能的垃圾回收中斷

    應(yīng)用程序剛剛分配非常多的對象,程序想在使用完這些對象后盡快的回收內(nèi)存空間

    在使用強制垃圾回收時,建議同時調(diào)用”GC.WaitForPendingFinalizers();”,這樣可以確定在程序繼續(xù)執(zhí)行之前,所有的可終結(jié)對象都必須執(zhí)行必要的清除工作。但是要注意,GC.WaitForPendingFinalizers()會在回收過程中掛起調(diào)用的線程。

    static void Main(string[] args)

    {

    ……

    GC.Collect();

    GC.WaitForPendingFinalizers();

    ……

    }

    每一次垃圾回收過程都會損耗性能,所以要盡量避免通過GC.Collect()進行強制垃圾回收,除非遇到了真的需要強制垃圾回收的情況。

    總結(jié)

    本文介紹了.NET垃圾回收機制的基本工作過程,垃圾回收器通過遍歷托管堆上的對象進行標記,然后清除所有的不可達對象;在托管堆上的對象都被設(shè)置了一個代,通過了代這個概念,垃圾回收的性能得到了優(yōu)化。

    更多信息請查看IT技術(shù)專欄

    更多信息請查看技術(shù)文章
    上一篇:高效的emacs
    易賢網(wǎng)手機網(wǎng)站地址:.NET垃圾回收:原理淺析

    2025國考·省考課程試聽報名

    • 報班類型
    • 姓名
    • 手機號
    • 驗證碼
    關(guān)于我們 | 聯(lián)系我們 | 人才招聘 | 網(wǎng)站聲明 | 網(wǎng)站幫助 | 非正式的簡要咨詢 | 簡要咨詢須知 | 新媒體/短視頻平臺 | 手機站點 | 投訴建議
    工業(yè)和信息化部備案號:滇ICP備2023014141號-1 云南省教育廳備案號:云教ICP備0901021 滇公網(wǎng)安備53010202001879號 人力資源服務(wù)許可證:(云)人服證字(2023)第0102001523號
    聯(lián)系電話:0871-65099533/13759567129 獲取招聘考試信息及咨詢關(guān)注公眾號:hfpxwx
    咨詢QQ:1093837350(9:00—18:00)版權(quán)所有:易賢網(wǎng)