.NET垃圾回收器(GC)原理淺析
作為.NET進(jìn)階內(nèi)容的一部分,垃圾回收器(簡(jiǎn)稱GC)是必須了解的內(nèi)容。本著“通俗易懂”的原則,本文將解釋CLR中垃圾回收器的工作原理。
基礎(chǔ)知識(shí)
托管堆(Managed Heap)
先來(lái)看MSDN的解釋:初始化新進(jìn)程時(shí),運(yùn)行時(shí)會(huì)為進(jìn)程保留一個(gè)連續(xù)的地址空間區(qū)域。這個(gè)保留的地址空間被稱為托管堆。
“托管堆也是堆”,為什么這樣說(shuō)呢?這么說(shuō)是希望大家不要被“術(shù)語(yǔ)”迷惑,這個(gè)知識(shí)點(diǎn)的前提是“值類型和引用類型的區(qū)別”。這里假設(shè)讀者已經(jīng)知道“值類型存儲(chǔ)在棧中,引用類型存儲(chǔ)在堆中。(引用類型的引用存儲(chǔ)在棧中)”這一重要概念。所以,根據(jù)這個(gè)理論,除值類型外,CLR要求所有資源都從托管堆分配。
托管堆維護(hù)著一個(gè)指針,這里命名為NextObjPtr,它指向下一個(gè)對(duì)象在堆中的分配位置。
CPU寄存器(CPU Register)
這個(gè)是計(jì)算機(jī)基礎(chǔ)知識(shí),這里復(fù)習(xí)一下,有助于對(duì)下面“根”概念的理解。
CPU寄存器是CPU自己的”臨時(shí)存儲(chǔ)器”,比內(nèi)存的存取還快。按與CPU遠(yuǎn)近來(lái)分,離得最近的是寄存器,然后緩存(計(jì)算機(jī)一、二、三級(jí)緩存),最后內(nèi)存。
根(Roots)
類中定義的任何靜態(tài)字段,方法的參數(shù),局部變量(僅限引用類型變量)等都是根,另外cpu寄存器中的對(duì)象指針也是根。根是CLR在堆之外可以找到的各種入口點(diǎn)。
對(duì)象可達(dá)與不可達(dá)(Objects reachable and unreachable)
如果一個(gè)根引用了堆中的一個(gè)對(duì)象,則該對(duì)象為“可達(dá)”,否則即是“不可達(dá)”。
垃圾回收的原因
從計(jì)算機(jī)組成的角度來(lái)講,所有的程序都是要駐留在內(nèi)存中運(yùn)行的。而內(nèi)存是一個(gè)限制因素(大小)。除此之外,托管堆也有大小限制。如果托管堆沒(méi)有大小限制,那C#的執(zhí)行速度要優(yōu)于c了(托管堆的結(jié)構(gòu)讓它有比c運(yùn)行時(shí)堆更快的對(duì)象分配速度)。因?yàn)榈刂房臻g和存儲(chǔ)的限制因素,托管堆要通過(guò)垃圾回收機(jī)制,來(lái)維持它的正常運(yùn)作,保證對(duì)象的分配,不會(huì)“內(nèi)存溢出”。
垃圾回收的基本原理
回收分為兩個(gè)階段: 標(biāo)記 –> 壓縮
標(biāo)記的過(guò)程,其實(shí)就是判斷對(duì)象是否可達(dá)的過(guò)程。當(dāng)所有的根都檢查完畢后,堆中將包含可達(dá)(已標(biāo)記)與不可達(dá)(未標(biāo)記)對(duì)象。
標(biāo)記完成后,進(jìn)入壓縮階段。在這個(gè)階段中,垃圾回收器線性的遍歷堆,以尋找不可達(dá)對(duì)象的連續(xù)內(nèi)存塊。并把可達(dá)對(duì)象移動(dòng)到這里以壓縮堆。這個(gè)過(guò)程有點(diǎn)類似于磁盤(pán)空間的碎片整理。
如上圖所示,綠色框表示可達(dá)對(duì)象,黃色框?yàn)椴豢蛇_(dá)對(duì)象。不可達(dá)對(duì)象清除后,移動(dòng)可達(dá)對(duì)象實(shí)現(xiàn)內(nèi)存壓縮(變得更緊湊)。
壓縮之后,“指向這些對(duì)象的指針”的變量和CPU寄存器現(xiàn)在都會(huì)失效,垃圾回收器必須重新訪問(wèn)所有根,并修改它們來(lái)指向?qū)ο蟮男聝?nèi)存位置。這會(huì)造成顯著的性能損失。這個(gè)損失也是托管堆的主要缺點(diǎn)。
基于以上特點(diǎn),垃圾回收引發(fā)的回收算法也是一項(xiàng)研究課題。因?yàn)槿绻娴鹊酵泄芏褲M才開(kāi)始執(zhí)行垃圾回收,那就真的太“慢”了。
垃圾回收算法 – 分代(Generation)算法
代是CLR垃圾回收器采用的一種機(jī)制,它唯一的目的就是提升應(yīng)用程序的性能。分代回收,速度顯然快于回收整個(gè)堆。
CLR托管堆支持3代:第0代,第1代,第2代。第0代的空間約為256KB,第1代約為2M,第2代約為10M。新構(gòu)造的對(duì)象會(huì)被分配到第0代。
如上圖所示,當(dāng)?shù)?代的空間滿時(shí),垃圾回收器啟動(dòng)回收,不可達(dá)對(duì)象(上圖C、E)會(huì)被回收,存活的對(duì)象被歸為第1代。
當(dāng)?shù)?代空間已滿,第1代也開(kāi)始有很多不可達(dá)對(duì)象以至空間將滿時(shí),這時(shí)兩代垃圾都將被回收。存活下來(lái)的對(duì)象(可達(dá)對(duì)象),第0代升為第1代,第1代升為第2代。
實(shí)際CLR的代回收機(jī)制更加“智能”,如果新創(chuàng)建的對(duì)象生存周期很短,第0代垃圾也會(huì)立刻被垃圾回收器回收(不用等空間分配滿)。另外,如果回收了第0代,發(fā)現(xiàn)還有很多對(duì)象“可達(dá)”,
并沒(méi)有釋放多少內(nèi)存,就會(huì)增大第0代的預(yù)算至512KB,回收效果就會(huì)轉(zhuǎn)變?yōu)椋豪厥盏拇螖?shù)將減少,但每次都會(huì)回收大量的內(nèi)存。如果還沒(méi)有釋放多少內(nèi)存,垃圾回收器將執(zhí)行
完全回收(3代),如果還是不夠,則會(huì)拋出“內(nèi)存溢出”異常。
也就是說(shuō),垃圾回收器會(huì)根據(jù)回收內(nèi)存的大小,動(dòng)態(tài)的調(diào)整每一代的分配空間預(yù)算!達(dá)到自動(dòng)優(yōu)化!
總結(jié)
垃圾回收背后有這樣一個(gè)基本的觀念:編程語(yǔ)言(大多數(shù)的)似乎總能訪問(wèn)無(wú)限的內(nèi)存。而開(kāi)發(fā)者可以一直分配、分配再分配——像魔法一樣,取之不盡用之不竭。
.NET垃圾回收器的基本工作原理是:通過(guò)最基本的標(biāo)記清除原理,清除不可達(dá)對(duì)象;再像磁盤(pán)碎片整理一樣壓縮、整理可用內(nèi)存;最后通過(guò)分代算法實(shí)現(xiàn)性能最優(yōu)化。
相關(guān)文章
WindowsForm移動(dòng)一個(gè)沒(méi)有標(biāo)題欄的窗口的方法
這篇文章主要介紹了WindowsForm移動(dòng)一個(gè)沒(méi)有標(biāo)題欄的窗口的方法,文中講解非常細(xì)致,代碼幫助大家更好的理解和學(xué)習(xí),感興趣的朋友可以了解下2020-07-07C#小知識(shí)之有趣的類型靜態(tài)構(gòu)造器
這篇文章主要介紹了C#小知識(shí)之有趣的類型靜態(tài)構(gòu)造器,本文直接給分實(shí)例代碼,然后分析了C#中的這一個(gè)有趣的現(xiàn)象,需要的朋友可以參考下2015-04-04C#實(shí)現(xiàn)功能強(qiáng)大的中國(guó)農(nóng)歷日歷操作類
這篇文章主要介紹了C#實(shí)現(xiàn)功能強(qiáng)大的中國(guó)農(nóng)歷日歷操作類,實(shí)例分析了C#操作時(shí)間及字符串的技巧,非常具有實(shí)用價(jià)值,需要的朋友可以參考下2015-03-03在Winform和WPF中注冊(cè)全局快捷鍵實(shí)現(xiàn)思路及代碼
如果注冊(cè)快捷鍵,RegisterHotKey中的fsModifiers參數(shù)為0,即None選項(xiàng),一些安全軟件會(huì)警報(bào),可能因?yàn)檫@樣就可以全局監(jiān)聽(tīng)鍵盤(pán)而造成安全問(wèn)題,感興趣的你可以參考下本文2013-02-02