【转】深入浅出图解C#堆与栈 C# Heap(ing) VS Stack(ing) 第六节 理解垃圾回收GC,提搞程序性能****
前言
雖然在.Net Framework 中我們不必考慮內(nèi)在管理和垃圾回收(GC),但是為了優(yōu)化應(yīng)用程序性能我們始終需要了解內(nèi)存管理和垃圾回收(GC)。另外,了解內(nèi)存管理可以幫助我們理解在每一個(gè)程序中定義的每一個(gè)變量是怎樣工作的。
簡介
這一節(jié)我們將介紹垃圾回收機(jī)制GC以及一些提搞程序性能的技巧。
?
繪圖Graphing
讓我們站在GC的角度研究一下。如果我們負(fù)責(zé)“扔垃圾”,我們需要制定一個(gè)有效的“扔垃圾”計(jì)劃。顯然,我們需要判斷哪些是垃圾,哪些不是。
為了決定哪些需要保留,我們假設(shè)任何沒有正在被使用的東西都是垃圾(如角落里堆積的破舊紙張,閣樓里一箱箱沒有用的過時(shí)產(chǎn)品,柜子里不用的衣服)。想像一下我們跟兩個(gè)好朋友生活在一起:JIT 和CLR。JIT和CLR不斷的跟蹤他們正在使用的東西,并給我們一個(gè)他們需要保留的東西列表。這個(gè)初始列表我們叫它“根(root)”列表。因?yàn)槲覀冇盟銎瘘c(diǎn)。我們將保持一個(gè)主列表去繪制一張圖,圖中分布著所有我們?cè)诜孔又行枰A魱|西。任何與主列表中有關(guān)聯(lián)的東西也被畫入圖中。如,我們保留電視就不要扔掉電視遙控器,所以電視遙控器也會(huì)被畫入圖中。我們保留電腦就不能扔掉顯示器鍵盤鼠標(biāo),同樣也把它們畫入圖中。
這就是GC怎么決定去保留對(duì)象的。GC會(huì)保留從JIT和CLR那收到的一個(gè)根(root)對(duì)象引用列表,然后遞歸搜索對(duì)象引用并決定什么需要保留。
這個(gè)根的構(gòu)成如下:
- 全局/靜態(tài) 指針。通過以靜態(tài)變量的方式保持對(duì)象的引用,來確保對(duì)象不會(huì)被GC回收。
- 棧里的指針。為了程序的執(zhí)行,我們不想扔掉那些程序線程始終需要的對(duì)象。
- CPU寄存器指針。托管堆里任何被CPU內(nèi)存地址指向的對(duì)象都需要被保留。
?
在上面的圖中,托管堆中的對(duì)象1,5被根Roots引用,3被1引用。對(duì)象1,5是被直接引用,3是通過遞歸查詢找到。如果關(guān)聯(lián)到我們之前的假設(shè),對(duì)象1是我們的電視,對(duì)象3則是電視遙控器。當(dāng)所有對(duì)象畫完后,我們開始進(jìn)行下一階段:垃圾清理。
?
GC垃圾清理Compacting
現(xiàn)在我們有了一張需要保留對(duì)象的關(guān)系圖,接下來進(jìn)行GC的清理。
?
圖中對(duì)象2和4被認(rèn)定為垃圾將被清理。清理對(duì)象2,復(fù)制(?memcpy?)對(duì)象3到2的位置。
由于對(duì)象3的地址變了,GC需要修復(fù)指針(紅色箭頭)。然后清理對(duì)象4,復(fù)制(?memcpy?)對(duì)象5到原來3的位置(譯外話:GC原則:堆中對(duì)象之間是沒有間隙的,以后會(huì)有文章專門介紹GC原理)。
?
?
最后清理完畢,新對(duì)象將被放到對(duì)象5的上面(譯外話:GC對(duì)一直管理一個(gè)指針指向新對(duì)象將被放置的地址,如黃色箭頭,以后會(huì)有文章專門介紹)。
了解GC原理可以幫助我們理解GC清理(復(fù)制?memcpy ,指針修復(fù)等)是怎么消耗掉很多資源的。很明顯,減少托管堆里對(duì)象的移動(dòng)(復(fù)制?memcpy?)可以提高GC清理的效率。
?
托管堆之外的終止化隊(duì)列Finalization Queue和終止化-可達(dá)隊(duì)列Freachable Queue
有些情況下,GC需要執(zhí)行特定代碼去清理非托管資源,如文件操作,數(shù)據(jù)庫連接,網(wǎng)絡(luò)連接等。一種可行性方案是使用析構(gòu)函數(shù)(終結(jié)器):
class Sample{
~Sample()
{
// FINALIZER: CLEAN UP HERE 終結(jié)器:在這里清理
}
}
譯外話:析構(gòu)函數(shù)會(huì)被內(nèi)部轉(zhuǎn)換成終結(jié)器override Finializer()
有終結(jié)器的對(duì)象在創(chuàng)建時(shí),同時(shí)在Finalization Queue里創(chuàng)建指向它們的指針(更正原文說的把對(duì)象放到Finalization Queue里):
上圖對(duì)象1,4,5實(shí)現(xiàn)了終結(jié)器,因此在Finalization Queue里創(chuàng)建指向它們的指針。讓我們看一下,當(dāng)對(duì)象2和4沒有被程序引用要被GC清理時(shí)會(huì)發(fā)生什么情況。
對(duì)象2會(huì)被以常規(guī)模式清理掉(見文章開始部分)。GC發(fā)現(xiàn)對(duì)象4有終結(jié)器,則會(huì)把Finalization Queue里指向它的指針移到Freachable Queue中,如下圖:
但是對(duì)象4并不被清理掉。有一個(gè)專門處理Freachable Queue的線程,當(dāng)它處理完對(duì)象4在Freachable Queue里的指針后,會(huì)把它移除。
這時(shí)對(duì)象4可以被清理了。當(dāng)下次GC清理時(shí)會(huì)把它移除掉。換句話說,?至少執(zhí)行?兩次GC清理才能把對(duì)象4清理掉,顯然會(huì)影響程序性能。
創(chuàng)建終結(jié)器,意味著創(chuàng)建了更多的工作給GC,也就會(huì)消耗更多資源影響程序性能。因此,當(dāng)你使用終結(jié)器時(shí)一定要確保你確實(shí)需要使用它。
更好的方法是使用?IDisposable接口。
public class ResourceUser : IDisposable{
#region IDisposable Members
public void Dispose()
{
// 在這里清理!!!
}
#endregion
}
實(shí)現(xiàn)?IDisposable接口的對(duì)象可以使用using關(guān)鍵字:
using (ResourceUser rec = new ResourceUser())
{
// 具體實(shí)現(xiàn)。。。
} // {}代碼塊結(jié)束時(shí),會(huì)調(diào)用DISPOSE方法
變量rec的作用域是大括號(hào)內(nèi),大括號(hào)外不可訪問。
?
靜態(tài)變量
class Olympics
{
public static Collection<Runner> TryoutRunners;
}
class Runner
{
private string _fileName;
private FileStream _fStream;
public void GetStats()
{
FileInfo fInfo = new FileInfo(_fileName);
_fStream = _fileName.OpenRead();
}
}
如果你初始化了TryoutRunners,那么它將永遠(yuǎn)不會(huì)被GC清理,因?yàn)橛徐o態(tài)指針一直指向初始化的對(duì)象。一旦調(diào)用了Runner里GetStats()方法,因?yàn)镚etStats()里面沒有文件關(guān)閉操作,它將永遠(yuǎn)被打開也不會(huì)被GC清理。我們可以看到程序的崩潰即將來臨。
?
總結(jié)
一些良好的操作可以提高程序的性能:
譯外話:
我會(huì)在以后的文章里更詳細(xì)的介紹GC垃圾回收機(jī)制,包括GC劃分的0代generation 0,1代generation 1,2代generation 2。有時(shí)只有一篇文章或一種圖解還是會(huì)讓人迷惑,所以下一篇介紹GC垃圾回收的內(nèi)容更詳細(xì),圖解也有不同。
?
?
翻譯:http://www.c-sharpcorner.com/UploadFile/rmcochran/csharp_memory_401282006141834PM/csharp_memory_4.aspx
總結(jié)
以上是生活随笔為你收集整理的【转】深入浅出图解C#堆与栈 C# Heap(ing) VS Stack(ing) 第六节 理解垃圾回收GC,提搞程序性能****的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 吉利汽车收购魅族杀入手机市场 同行表态:
- 下一篇: 冰雹杀伤力有多大?专家:30克冰雹等于从