.net 垃圾回收
垃圾回收器幫我們處理了內存中不在使用的對象,提高了機器的性能,讓開發人員輕鬆了很多。
你真的瞭解垃圾回收嗎?
或許你知道垃圾回收,聽說過是通過標記回收,可是怎麼標記回收呢就不是很清楚了,好吧,如果不清楚就繼續往下看。如果你是大神對這塊瞭如執掌,請直接跳過,歡迎來提不同的意見。
1、我們先來聊一下內存分配:
代碼中聲明變量是需要向內存申請地址的,內存呢又分託管堆和棧,我們今天主要聊的就是託管堆內存
啥事託管堆內存呢?想必各位也心中知道,不知道的自行百度谷歌去。
寫代碼中凡是需要使用new聲明的變量都是引用類型變量,使用的都是託管堆內存地址,那聲明瞭一個對象,需要分配多大的控件呢?
1.1、這個時候就需要計算類型的字段需要的字節數了
1.2、引用類型對象開銷的字節數還需要(類型對象指針和同步索引塊)
在32位應用中,這多出來的兩個字段各需32位字節地址空間,所以每個對象需要多佔用8個字節的地址控件
在64位應用中,這多出來的兩個字段各需64位字節地址空間,所以每個對象需要多佔用16個字節的地址控件
1.3、內存申請後,CLR會檢查保留區是否能夠提供分配對象所需的字節數,使用new 聲明的對象會向託管堆請求地址分配,並返回對象地址,NextObjPtr指針會加上對象佔據的字節數,得到一個新值
2、垃圾回收-Go Go Go
垃圾回收的基本邏輯:垃圾回收器會檢查託管堆中是否又應用程序不再使用的任何對象,如果有,它們使用的內存就可以回收了。
回收之前的託管堆如下:
下面我們來聊一下標記回收的整個過程:
2.1、首先,應用有一組根(root)每個根都是一個存儲位置,其中包含指向引用類型對象的一個指針,指針要麼引用託管堆中的一個對象,要麼爲null
例如:類型中定義的任何靜態字段被認爲是一個根
任何方法參數或局部變量也被認爲是一個根,只有引用類型的變量才被認爲是一個根,值類型不能被認爲是根。
2.2、垃圾回收的第一階段,標記階段:
這時,垃圾回收器會沿着線程棧上行以檢查所有根,如果發現一個根引用了一個對象,就在對象 “同步索引塊”上開啓一位---標記,
以遞歸的方式遍歷所有可達的對象。如果垃圾回收器試圖標記一個先前標記過的對象,就會停止沿這個路徑走下去。
這個行爲有兩個目的:
1、垃圾回收器不會多次遍歷一個對象,所以性能得到顯著增強
2、如果對象存在循環鏈表,可以避免無線循環。
檢查完所有的根之後,堆中將包含一組已標記和未標記的對象,已標記的對象是代碼可達的對象,而未標記的對象是不可達的,不可達的對象被認爲是垃圾,它們佔用的內存是可以被回收的
垃圾回收之後的託管堆如下:
2.3、垃圾回收的第二階段,壓縮階段:
這個時候該回收內存空間已經都回收了,空出來的內存可能是前頭一塊,中間一塊,後邊又一塊。
垃圾回收器線性遍歷堆,以尋找未標記對象的連續內存塊,如果發現內存塊比較小,則忽略,如果發現大的,可用的連續內存塊,垃圾回收器會把非垃圾的對象移動到這裏以壓縮堆。
參考:CLR Via C#(第三版)