天天看點

深入淺出圖解C#堆與棧 C# Heap(ing) VS Stack(ing) 第六節 了解垃圾回收GC,提搞程式性能

了解垃圾回收GC,提搞程式性能

目錄

深入淺出圖解C#堆與棧 C# Heap(ing) VS Stack(ing) 第一節 了解堆與棧

深入淺出圖解C#堆與棧 C# Heap(ing) VS Stack(ing) 第二節 棧基本工作原理

深入淺出圖解C#堆與棧 C# Heap(ing) VS Stack(ing) 第三節 棧與堆,值類型與引用類型

深入淺出圖解C#堆與棧 C# Heap(ing) VS Stack(ing) 第四節 參數傳遞對堆棧的影響 1

深入淺出圖解C#堆與棧 C# Heap(ing) VS Stack(ing) 第四節 參數傳遞對堆棧的影響 2

深入淺出圖解C#堆與棧 C# Heap(ing) VS Stack(ing) 第五節 引用類型複制問題及用克隆接口ICloneable修複

深入淺出圖解C#堆與棧 C# Heap(ing) VS Stack(ing) 第六節 了解垃圾回收GC,提搞程式性能

前言

雖然在.Net Framework 中我們不必考慮内在管理和垃圾回收(GC),但是為了優化應用程式性能我們始終需要了解記憶體管理和垃圾回收(GC)。另外,了解記憶體管理可以幫助我們了解在每一個程式中定義的每一個變量是怎樣工作的。

簡介

這一節我們将介紹垃圾回收機制GC以及一些提搞程式性能的技巧。

繪圖Graphing

讓我們站在GC的角度研究一下。如果我們負責“扔垃圾”,我們需要制定一個有效的“扔垃圾”計劃。顯然,我們需要判斷哪些是垃圾,哪些不是。

為了決定哪些需要保留,我們假設任何沒有正在被使用的東西都是垃圾(如角落裡堆積的破舊紙張,閣樓裡一箱箱沒有用的過時産品,櫃子裡不用的衣服)。想像一下我們跟兩個好朋友生活在一起:JIT 和CLR。JIT和CLR不斷的跟蹤他們正在使用的東西,并給我們一個他們需要保留的東西清單。這個初始清單我們叫它“根(root)”清單。因為我們用它做起點。我們将保持一個主清單去繪制一張圖,圖中分布着所有我們在房子中需要保留東西。任何與主清單中有關聯的東西也被畫入圖中。如,我們保留電視就不要扔掉電視遙控器,是以電視遙控器也會被畫入圖中。我們保留電腦就不能扔掉顯示器鍵盤滑鼠,同樣也把它們畫入圖中。

這就是GC怎麼決定去保留對象的。GC會保留從JIT和CLR那收到的一個根(root)對象引用清單,然後遞歸搜尋對象引用并決定什麼需要保留。

這個根的構成如下:

  • 全局/靜态 指針。通過以靜态變量的方式保持對象的引用,來確定對象不會被GC回收。
  • 棧裡的指針。為了程式的執行,我們不想扔掉那些程式線程始終需要的對象。
  • CPU寄存器指針。托管堆裡任何被CPU記憶體位址指向的對象都需要被保留。
深入淺出圖解C#堆與棧 C# Heap(ing) VS Stack(ing) 第六節 了解垃圾回收GC,提搞程式性能

在上面的圖中,托管堆中的對象1,5被根Roots引用,3被1引用。對象1,5是被直接引用,3是通過遞歸查詢找到。如果關聯到我們之前的假設,對象1是我們的電視,對象3則是電視遙控器。當所有對象畫完後,我們開始進行下一階段:垃圾清理。

GC垃圾清理Compacting

現在我們有了一張需要保留對象的關系圖,接下來進行GC的清理。

深入淺出圖解C#堆與棧 C# Heap(ing) VS Stack(ing) 第六節 了解垃圾回收GC,提搞程式性能

圖中對象2和4被認定為垃圾将被清理。清理對象2,複制(memcpy )對象3到2的位置。

深入淺出圖解C#堆與棧 C# Heap(ing) VS Stack(ing) 第六節 了解垃圾回收GC,提搞程式性能

由于對象3的位址變了,GC需要修複指針(紅色箭頭)。然後清理對象4,複制(memcpy )對象5到原來3的位置(譯外話:GC原則:堆中對象之間是沒有間隙的,以後會有文章專門介紹GC原理)。

深入淺出圖解C#堆與棧 C# Heap(ing) VS Stack(ing) 第六節 了解垃圾回收GC,提搞程式性能
深入淺出圖解C#堆與棧 C# Heap(ing) VS Stack(ing) 第六節 了解垃圾回收GC,提搞程式性能

最後清理完畢,新對象将被放到對象5的上面(譯外話:GC對一直管理一個指針指向新對象将被放置的位址,如黃色箭頭,以後會有文章專門介紹)。

了解GC原理可以幫助我們了解GC清理(複制memcpy ,指針修複等)是怎麼消耗掉很多資源的。很明顯,減少托管堆裡對象的移動(複制memcpy )可以提高GC清理的效率。

托管堆之外的終止化隊列Finalization Queue和終止化-可達隊列Freachable Queue

有些情況下,GC需要執行特定代碼去清理非托管資源,如檔案操作,資料庫連接配接,網絡連接配接等。一種可行性方案是使用析構函數(終結器):

深入淺出圖解C#堆與棧 C# Heap(ing) VS Stack(ing) 第六節 了解垃圾回收GC,提搞程式性能

譯外話:析構函數會被内部轉換成終結器override Finializer()

有終結器的對象在建立時,同時在Finalization Queue裡建立指向它們的指針(更正原文說的把對象放到Finalization Queue裡):

深入淺出圖解C#堆與棧 C# Heap(ing) VS Stack(ing) 第六節 了解垃圾回收GC,提搞程式性能

上圖對象1,4,5實作了終結器,是以在Finalization Queue裡建立指向它們的指針。讓我們看一下,當對象2和4沒有被程式引用要被GC清理時會發生什麼情況。

對象2會被以正常模式清理掉(見文章開始部分)。GC發現對象4有終結器,則會把Finalization Queue裡指向它的指針移到Freachable Queue中,如下圖:

深入淺出圖解C#堆與棧 C# Heap(ing) VS Stack(ing) 第六節 了解垃圾回收GC,提搞程式性能

但是對象4并不被清理掉。有一個專門處理Freachable Queue的線程,當它處理完對象4在Freachable Queue裡的指針後,會把它移除。

深入淺出圖解C#堆與棧 C# Heap(ing) VS Stack(ing) 第六節 了解垃圾回收GC,提搞程式性能

這時對象4可以被清理了。當下次GC清理時會把它移除掉。換句話說,至少執行兩次GC清理才能把對象4清理掉,顯然會影響程式性能。

建立終結器,意味着建立了更多的工作給GC,也就會消耗更多資源影響程式性能。是以,當你使用終結器時一定要確定你确實需要使用它。

更好的方法是使用IDisposable接口。

深入淺出圖解C#堆與棧 C# Heap(ing) VS Stack(ing) 第六節 了解垃圾回收GC,提搞程式性能

實作IDisposable接口的對象可以使用using關鍵字:

深入淺出圖解C#堆與棧 C# Heap(ing) VS Stack(ing) 第六節 了解垃圾回收GC,提搞程式性能

變量rec的作用域是大括号内,大括号外不可通路。

靜态變量

深入淺出圖解C#堆與棧 C# Heap(ing) VS Stack(ing) 第六節 了解垃圾回收GC,提搞程式性能

如果你初始化了TryoutRunners,那麼它将永遠不會被GC清理,因為有靜态指針一直指向初始化的對象。一旦調用了Runner裡GetStats()方法,因為GetStats()裡面沒有檔案關閉操作,它将永遠被打開也不會被GC清理。我們可以看到程式的崩潰即将來臨。

總結

一些良好的操作可以提高程式的性能:

1.清理。不要打開資源而不關閉它。關閉所有你打開的連接配接。盡可能快的清理所有非托管資源。一般規則:使用非托管對象,初始化越晚越好,清理越早越好。

2.不要過度引用。合理使用引用對象。如果某一個對象還存在沒有被GC清理,所有它引用的對象都将不會被GC清理,如此遞歸下去。。。當我們完成使用一個引用對象時,把它設為NULL(視你的情況而定,注意不要産生空引用異常)。當引用少了,GC開始建立清理關系圖graphing時過程就簡單一些了,進而提高程式性能。

3.謹慎使用終結器Finalizaer或析構函數。能使用IDisposible代替就使用IDisposible。

4.保持對象及其成員的緊湊。如果聲明一個對象并且它由多個子對象組成,盡可能的把它們放在一起初始化,好讓它們所在的記憶體空間緊湊。GC複制這樣的一大塊記憶體比複制分散的記憶體碎片要容易。

原文連接配接:https://blog.csdn.net/leewhoee/article/details/17109201

譯文連接配接:http://www.c-sharpcorner.com/UploadFile/rmcochran/csharp_memory_401282006141834PM/csharp_memory_4.aspx

繼續閱讀