GC

C#的 GC工作原理基礎(chǔ)

作為一位C++出身的C#程序員,我最初對垃圾收集(GC)抱有懷疑態(tài)度软吐,懷疑它是否能夠穩(wěn)定高效的運作瘩将;而到了現(xiàn)在,我自己不得不說我已經(jīng)逐漸習慣并依賴GC與我的程序“共同奔跑”了凹耙,對“delete”這個習慣于充當罪魁禍首的關(guān)鍵字也漸漸產(chǎn)生了陌生感姿现。然而實踐證明,我對GC的過分信賴卻招致了很多意想不到的錯誤肖抱,這也激勵了我對GC的運作機制作深入一步的了解备典。隨后我開始翻書,查資料意述,終于對GC有了一個比較完整的理解(但遠遠算不上深入)提佣。有人也許會說:“研究GC的內(nèi)部機制有什么價值嗎?我們是搞應(yīng)用程序開發(fā)的荤崇,客戶的機器可以達到很高的配置拌屏,內(nèi)存資源不是問題√焓裕”這種說法明顯是認為“垃圾收集=內(nèi)存釋放”了槐壳,其實在垃圾收集中,造成最多麻煩的往往不是內(nèi)存量喜每,而是在內(nèi)存釋放之外,GC暗地里為我們做的繁雜事務(wù)(例如非托管資源的清理和釋放)雳攘。如果你對GC的基本運作還不了解带兜,而又沒有時間仔細閱讀眾多技術(shù)資料的話,那么我的這幾篇文章或許對你能有一些幫助吨灭。

下面就從資源的分配和釋放入手刚照,先了解一下背景知識。

一. 托管資源的分配

CLR在運行時管理著一段內(nèi)存地址空間(虛擬地址空間喧兄,在運行中會映射到物理內(nèi)存地址中)无畔,分為“托管堆”和“棧”兩部分吠冤,棧用于存儲值類型數(shù)據(jù)浑彰,它會在方法執(zhí)行結(jié)束后自動銷毀其中引用的值類型變量,這一部分不屬于垃圾收集的范圍拯辙。托管堆用于引用類型的變量存儲郭变,是垃圾收集的關(guān)鍵陣地颜价。

托管堆是一段連續(xù)的地址空間,其中所分配出去的空間呈現(xiàn)出類似數(shù)組形態(tài)的隊列結(jié)構(gòu):

NextObjPtr是托管堆所維護的一個內(nèi)存指針诉濒,指示下一個對象分配的內(nèi)存起始地址周伦,它會隨著內(nèi)存的分配而不斷移動(當然也會隨著內(nèi)存垃圾回收而發(fā)生移動),永遠指向下一個空閑的地址未荒。

到了這里专挪,我們不妨與C++比較一下內(nèi)存分配機制的效率(對效率不感興趣的大可以跳過:)),順便讓C++的朋友們打消一些對CLR分配內(nèi)存效率的疑慮片排。在查找空閑內(nèi)存空間時寨腔,CLR只需要在NextObjPtr處直接留出指定大小的空間提供給數(shù)據(jù)初始化,然后計算新的空閑地址并重置NextObjPtr指針即可划纽。而在C/C++中脆侮,在分配內(nèi)存之前先要遍歷一遍內(nèi)存占用的鏈表以查找合適大小的內(nèi)存塊,然后再修改此鏈表勇劣,這樣也很容易產(chǎn)生內(nèi)存碎塊靖避,使得內(nèi)存分配性能下降。很明顯比默,.NET的分配方式效率更高幻捏。但是這種效率是以GC的勞動為代價的。

二. 垃圾判定

要進行垃圾收集命咐,首先要知道什么是垃圾篡九。GC通過遍歷應(yīng)用程序中的“根”來尋找垃圾。我們可以認為根是一個指向引用類型對象內(nèi)存地址的指針醋奠。如果一個對象沒有了根榛臼,就是它不再被任何位置所引用,那么它就是垃圾的候選者了窜司。

值得注意的一點是沛善,對象可能在其生存期結(jié)束之前就被列入垃圾名單,甚至已經(jīng)被GC所暗殺塞祈!那是因為對象可能在生存期的某一時刻已經(jīng)不再被引用金刁,如果在這個時候執(zhí)行垃圾收集,那么這個不幸的對象極有可能已經(jīng)被列為垃圾并被銷毀(為什么說是“可能”呢议薪?因為它不一定在GC的視力范圍內(nèi)尤蛮。后面講到“代齡”時會詳細介紹相關(guān)細節(jié))。

1publicstaticvoid Main()

2 {

3string sGarbage= "I'm here";

4

5//下面的代碼沒有再引用s斯议,它已經(jīng)成為垃圾對象---當然产捞,這樣的代碼本身也是垃圾;

6//此時如果執(zhí)行垃圾收集,則sGarbage可能已經(jīng)魂歸西天

7

8 Console.WriteLine("Main() is end");

9 }

三. 對象代齡

盡管GC總是在默默為我們勞動捅位,但它畢竟是由人創(chuàng)造的轧葛,人會偷懶搂抒,它也會。為了減少每次的工作量尿扯,它總是希望能夠減少工作的范圍求晶;它堅信,越晚創(chuàng)建的對象往往越短命衷笋,因此它會集中精力處理這一部分的內(nèi)存區(qū)域芳杏,暫且擱置其他部分。GC引入“代齡”的概念來劃分對象生存級別辟宗。

CLR初始化后的第一批被創(chuàng)建的對象被列為0代對象爵赵。CLR會為0代對象設(shè)定一個容量限制,當創(chuàng)建的對象大小超過這個設(shè)定的容量上限時泊脐,GC就會開始工作空幻,工作的范圍是0代對象所處的內(nèi)存區(qū)域,然后開始搜尋垃圾對象容客,并釋放內(nèi)存秕铛。當GC工作結(jié)束后,幸存的對象將被列為第1代對象而保留在第1代對象的區(qū)域內(nèi)缩挑。此后新創(chuàng)建的對象將被列為新的一批0代對象但两,直到0代的內(nèi)存區(qū)域再次被填滿,然后會針對0代對象區(qū)域進行新一輪的垃圾收集供置,之后這些0代對象又會列為第1代對象谨湘,并入第1代區(qū)域內(nèi)。第1代區(qū)域起初也會被設(shè)上一個容量限制值芥丧,等到第1代對象大小超過了這個限制之后紧阔,GC就會擴大戰(zhàn)場,對第1代區(qū)域也做一次垃圾收集续担,之后寓辱,又一次幸存下來的對象將會提升一個代齡,成為第2代對象赤拒。

可見,有一些對象雖然符合垃圾的所有條件诱鞠,但它們?nèi)绻堑?代(甚至是第2代老臣)對象挎挖,并且第1代的分配量還小于被設(shè)定的限制值時,這些垃圾對象就不會被GC發(fā)現(xiàn)航夺,并且可以繼續(xù)存活下去蕉朵。

另外,GC還會在工作過程中汲取經(jīng)驗阳掐,根據(jù)應(yīng)用程序的特點而自動調(diào)整每代對象區(qū)域的容量始衅,從而可以更高效的工作冷蚂。

應(yīng)該了解的垃圾收集機制(二)

對于大多數(shù)應(yīng)用而言,了解垃圾收集機制的主要動機并不是為了對內(nèi)存“省吃儉用”汛闸,而是為了處理非托管資源的控制問題蝙茶,這些問題往往跟內(nèi)存的大小沒有什么關(guān)系。例如對一個文件進行操作诸老,該何時關(guān)閉文件隆夯,關(guān)閉文件時要注意什么問題,如果忘了關(guān)閉會帶來什么后果别伏?這些都是我們需要認真考慮的蹄衷,無論你的內(nèi)存有多大:)

對于這一類的操作,我們不能依賴GC幫我們做厘肮,因為它并不知道我們在釋放時想干什么愧口,它甚至不知道自己該干什么!我們不得不自己動手來編寫處理代碼类茂。當然耍属,微軟已經(jīng)為我們搭好了框架,就是這兩個函數(shù):Finalize和Dispose大咱。它們也代表了非托管清理的兩種方式:自動和手動恬涧。

一. Finalize

Finalize很像C++的析構(gòu)函數(shù),我們在代碼中的實現(xiàn)形式為這與C++的析構(gòu)函數(shù)在形式上完全一樣碴巾,但它的調(diào)用過程卻大不相同溯捆。

~ClassName() {//釋放你的非托管資源}

比如類A中實現(xiàn)了Finalize函數(shù),在A的一個對象a被創(chuàng)建時(準確的說應(yīng)該是構(gòu)造函數(shù)被調(diào)用之前)厦瓢,它的指針被插入到一個finalization鏈表中提揍;在GC運行時,它將查找finalization鏈表中的對象指針煮仇,如果此時a已經(jīng)是垃圾對象的話劳跃,它會被移入一個freachable隊列中,最后GC會調(diào)用一個高優(yōu)先級線程浙垫,這個線程專門負責遍歷freachable隊列并調(diào)用隊列中所有對象的Finalize方法刨仑,至此,對象a中的非托管資源才得到了釋放(當然前提是你正確實現(xiàn)了它的Finalize方法)夹姥,而a所占用的內(nèi)存資源則必需等到下一次GC才能得到釋放杉武,所以一個實現(xiàn)了Finalize方法的對象必需等兩次GC才能被完全釋放。

由于Finalize是由GC負責調(diào)用辙售,所以可以說是一種自動的釋放方式轻抱。但是這里面要注意兩個問題:第一,由于無法確定GC何時會運作旦部,因此可能很長的一段時間里對象的資源都沒有得到釋放祈搜,這對于一些關(guān)鍵資源而言是非常要命的较店。第二,由于負責調(diào)用Finalize的線程并不保證各個對象的Finalize的調(diào)用順序容燕,這可能會帶來微妙的依賴性問題梁呈。如果你在對象a的Finalize中引用了對象b,而a和b兩者都實現(xiàn)了Finalize缰趋,那么如果b的Finalize先被調(diào)用的話捧杉,隨后在調(diào)用a的Finalize時就會出現(xiàn)問題,因為它引用了一個已經(jīng)被釋放的資源秘血。因此味抖,在Finalize方法中應(yīng)該盡量避免引用其他實現(xiàn)了Finalize方法的對象。

可見灰粮,這種“自動”釋放資源的方法并不能滿足我們的需要仔涩,因為我們不能顯示的調(diào)用它(只能由GC調(diào)用),而且會產(chǎn)生依賴型問題粘舟。我們需要更準確的控制資源的釋放熔脂。

二. Dispose

Dispose是提供給我們顯示調(diào)用的方法。由于對Dispose的實現(xiàn)很容易出現(xiàn)問題柑肴,所以在一些書籍上(如《Effective C#》和《Applied Microsoft.Net Framework Programming》)給出了一個特定的實現(xiàn)模式:

class DisposePattern :IDisposable

{

private System.IO.FileStream fs = new System.IO.FileStream("test.txt", System.IO.FileMode.Create);

~DisposePattern()

{

Dispose(false);

}

IDisposable Members#region IDisposable Members

public void Dispose()

{

//告訴GC不需要再調(diào)用Finalize方法霞揉,

//因為資源已經(jīng)被顯示清理

GC.SupdivssFinalize(this);

Dispose(true);

}

endregion

protected virtual void Dispose(bool disposing)

{

//由于Dispose方法可能被多線程調(diào)用,

//所以加鎖以確保線程安全

lock (this)

{

if (disposing)

{

//說明對象的Finalize方法并沒有被執(zhí)行晰骑,

//在這里可以安全的引用其他實現(xiàn)了Finalize方法的對象

}

if (fs != null)

{

fs.Dispose();

fs = null; //標識資源已經(jīng)清理适秩,避免多次釋放

}

}

}

}

在注釋中已經(jīng)有了比較清楚的描述,另外還有一點需要說明:如果DisposePattern類是派生自基類B硕舆,而B是一個實現(xiàn)了Dispose的類秽荞,那么DisposePattern中只需要override基類B的帶參的Dispose方法即可,而不需要重寫無參的Dispose和Finalize方法抚官,此時Dispose的實現(xiàn)為:

class DerivedClass : DisposePattern

{

protected override void Dispose(bool disposing)

{

lock (this)

{

try

{

//清理自己的非托管資源扬跋,

//實現(xiàn)模式與DisposePattern相同

}

finally

{

base.Dispose(disposing);

}

}

}

}

當然,如果DerivedClass本身沒有什么資源需要清理凌节,那么就不需要重寫Dispose方法了钦听,正如我們平時做的一些對話框,雖然都是繼承于System.Windows.Forms.Form倍奢,但我們常常不需要去重寫基類Form的Dispose方法彪见,因為本身沒有什么非托管的咚咚需要釋放。

了解GC的脾性在很多時候是非常必要的娱挨,起碼在出現(xiàn)資源泄漏問題的時候你不至于手足無措。我寫過一個生成excel報表的控件捕犬,其中對excel對象的釋放就讓我忙活了一陣跷坝。如果你做過excel開發(fā)的話酵镜,可能也遇到過結(jié)束excel進程之類的問題,特別是包裝成一個供別人調(diào)用的庫時柴钻,何時釋放excel對象以確保進程結(jié)束是一個關(guān)鍵問題淮韭。當然,GC的內(nèi)部機制非常復雜贴届,還有許多內(nèi)容可挖靠粪,但了解所有細節(jié)的成本太高,只需了解基礎(chǔ)毫蚓,夠用就好占键。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市元潘,隨后出現(xiàn)的幾起案子畔乙,更是在濱河造成了極大的恐慌,老刑警劉巖翩概,帶你破解...
    沈念sama閱讀 216,496評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件牲距,死亡現(xiàn)場離奇詭異,居然都是意外死亡钥庇,警方通過查閱死者的電腦和手機牍鞠,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,407評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來评姨,“玉大人难述,你說我怎么就攤上這事〔瘟” “怎么了龄广?”我有些...
    開封第一講書人閱讀 162,632評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長蕴侧。 經(jīng)常有香客問我择同,道長,這世上最難降的妖魔是什么净宵? 我笑而不...
    開封第一講書人閱讀 58,180評論 1 292
  • 正文 為了忘掉前任敲才,我火速辦了婚禮,結(jié)果婚禮上择葡,老公的妹妹穿的比我還像新娘紧武。我一直安慰自己,他們只是感情好敏储,可當我...
    茶點故事閱讀 67,198評論 6 388
  • 文/花漫 我一把揭開白布阻星。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪妥箕。 梳的紋絲不亂的頭發(fā)上滥酥,一...
    開封第一講書人閱讀 51,165評論 1 299
  • 那天,我揣著相機與錄音畦幢,去河邊找鬼坎吻。 笑死,一個胖子當著我的面吹牛宇葱,可吹牛的內(nèi)容都是我干的瘦真。 我是一名探鬼主播,決...
    沈念sama閱讀 40,052評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼黍瞧,長吁一口氣:“原來是場噩夢啊……” “哼诸尽!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起雷逆,我...
    開封第一講書人閱讀 38,910評論 0 274
  • 序言:老撾萬榮一對情侶失蹤弦讽,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后膀哲,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體往产,經(jīng)...
    沈念sama閱讀 45,324評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,542評論 2 332
  • 正文 我和宋清朗相戀三年某宪,在試婚紗的時候發(fā)現(xiàn)自己被綠了仿村。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,711評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡兴喂,死狀恐怖蔼囊,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情衣迷,我是刑警寧澤畏鼓,帶...
    沈念sama閱讀 35,424評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站壶谒,受9級特大地震影響云矫,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜汗菜,卻給世界環(huán)境...
    茶點故事閱讀 41,017評論 3 326
  • 文/蒙蒙 一让禀、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧陨界,春花似錦巡揍、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,668評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春缀皱,著一層夾襖步出監(jiān)牢的瞬間斗这,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,823評論 1 269
  • 我被黑心中介騙來泰國打工啤斗, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人赁咙。 一個月前我還...
    沈念sama閱讀 47,722評論 2 368
  • 正文 我出身青樓钮莲,卻偏偏與公主長得像,于是被迫代替她去往敵國和親彼水。 傳聞我的和親對象是個殘疾皇子崔拥,可洞房花燭夜當晚...
    茶點故事閱讀 44,611評論 2 353

推薦閱讀更多精彩內(nèi)容

  • 概念1:內(nèi)存自動管理,1、提高了軟件開發(fā)的抽象度凤覆;2链瓦、程序員可以將精力集中在實際的問題上而不用分心來管理內(nèi)存的問題...
    諸子百家誰的天下閱讀 1,775評論 2 1
  • [TOC] 內(nèi)存管理 一、托管堆基礎(chǔ) 在面向?qū)ο笾卸㈣耄總€類型代表一種可使用的資源慈俯,要使用該資源,必須為代表資源的類...
    _秦同學_閱讀 3,801評論 0 3
  • 原文閱讀 前言 這段時間懈怠了拥峦,罪過贴膘! 最近看到有同事也開始用上了微信公眾號寫博客了,挺好的~給他們點贊略号,這博客我...
    碼農(nóng)戲碼閱讀 5,961評論 2 31
  • 什么是GC GC如其名刑峡,就是垃圾收集,當然這里僅就內(nèi)存而言玄柠。Garbage Collector(垃圾收集器突梦,在不至...
    此年此景閱讀 992評論 0 1
  • 原文鏈接:https://www.cnblogs.com/byue/p/5734779.html 一個優(yōu)秀Java...
    Easy的幸福閱讀 497評論 0 2