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

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

創(chuàng)建對(duì)象

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

Student s1 = new Student();

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

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

new關(guān)鍵字

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

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

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

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

檢查托管堆,確保有足夠的空間來(lái)存放新建的對(duì)象

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

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

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

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

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

應(yīng)用程序的根

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

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

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

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

傳遞進(jìn)一個(gè)方法的對(duì)象參數(shù)的引用

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

任何引用對(duì)象的CPU寄存器

垃圾回收可以分為兩個(gè)步驟:

標(biāo)記對(duì)象

壓縮托管堆

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

標(biāo)記對(duì)象

在垃圾回收的過(guò)程中,垃圾回收器會(huì)認(rèn)為托管堆中的所有對(duì)象都是垃圾,然后垃圾回收器會(huì)檢查所有的根。為此,CLR會(huì)建立一個(gè)對(duì)象圖,代表托管堆上所有可達(dá)對(duì)象。

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

當(dāng)發(fā)現(xiàn)有根引用了托管堆中的對(duì)象A時(shí),垃圾回收器會(huì)對(duì)此對(duì)象A進(jìn)行標(biāo)記

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

重復(fù)步驟二,檢測(cè)Globales根,這次標(biāo)記對(duì)象D

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

所有的根對(duì)象都檢查完之后,有標(biāo)記的對(duì)象就是可達(dá)對(duì)象,未標(biāo)記的對(duì)象就是不可達(dá)對(duì)象。

壓縮托管堆

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

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

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

對(duì)象的代

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

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

0代:從沒(méi)有被標(biāo)記為回收的新分配的對(duì)象

1代:在上一次垃圾回收中沒(méi)有被回收的對(duì)象

2代:在一次以上的垃圾回收后仍然沒(méi)有被回收的對(duì)象

下面還是通過(guò)一個(gè)例子看看代這個(gè)概念(灰色代表不可達(dá)對(duì)象):

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

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

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

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

System.GC

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

看一個(gè)簡(jiǎn)單的例子:

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();

}

}

程序的輸出為:

從這個(gè)輸出,我們也可以驗(yàn)證代的概念,每次垃圾清理后,如果一個(gè)對(duì)象沒(méi)有被清理,那么它的代就會(huì)提高。

強(qiáng)制垃圾回收

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

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

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

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

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

static void Main(string[] args)

{

……

GC.Collect();

GC.WaitForPendingFinalizers();

……

}

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

總結(jié)

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

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

更多信息請(qǐng)查看技術(shù)文章
上一篇:高效的emacs
易賢網(wǎng)手機(jī)網(wǎng)站地址:.NET垃圾回收:原理淺析
由于各方面情況的不斷調(diào)整與變化,易賢網(wǎng)提供的所有考試信息和咨詢回復(fù)僅供參考,敬請(qǐng)考生以權(quán)威部門公布的正式信息和咨詢?yōu)闇?zhǔn)!

2025國(guó)考·省考課程試聽(tīng)報(bào)名

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