歡迎光臨
每天分享高質量文章

淺談c#垃圾回收機制(GC)

寫了一個window服務,迴圈更新sqlite記錄,記憶體一點點穩步增長。三天后,記憶體上限溢位。於是,我從自己的代碼入手,查找到底哪兒占用記憶體釋放不掉,最終明確是呼叫servicestack.ormlite更新sqlite資料庫造成的。至於是不是框架問題,可能性不大,因為本地模擬執行的代碼沒有任何問題。我覺得應該是orm在執行資料庫更新後,物件還在被取用造成的。這裡,我貼出一個偽代碼:

我的猜測到底對不對呢?現在還不知道。不過在探尋答案的時候,對GC的相關機制詳細地瞭解了一遍。

一、什麼是GC?

官網中有這麼一句話:

The garbage collector is a common language runtime component that controls the allocation and release of managed memory。

原來GC是CLR的一個組件,它控制記憶體的分配與釋放。

二、托管堆和CLR堆管理器

我們知道c#中的取用型別,分配在堆上。所謂的堆,就是一大塊連續的記憶體地址。CLR堆管理器負責記憶體的分配、釋放。堆又分為小物件堆和大物件堆。它的記憶體分配流程如下:

                                                  圖片來源《.NET高級除錯》pdf

CLR加載時,就會分配堆。

 三、GC的工作機制

 GC有三個假設:

1、如果沒有特別宣告,所有的物件都是垃圾(通過取用追蹤物件是否為垃圾)

2、假設托管堆上所有的物件的活躍時間都是短暫的(相對於長久活躍的物件來說,GC將更頻繁地收集短暫活躍的物件)

3、通過代跟蹤物件的持續時間

以下是官方文件給出的和這三個假設一致

The garbage collector in the common language runtime supports object aging using generations

Objects created more recently are part of newer generations, and have lower generation numbers than objects created earlier in the application life cycle.

Objects in the most recent generation are in generation 0. This implementation of the garbage collector supports three generations of objects, generations 0, 1, and 2

每代都有自己的堆,假如0代的堆滿了,就會觸發GC,然後把依然有取用的物件升級,放到1代物件。最後壓縮堆,把剩餘的堆空間合併到一塊。1代物件也是如此操作。但到了2代,就處理不同了。2代的堆可能是大物件堆,它的壓縮代價過於高昂,所以只是合併相鄰的空間。

                                                    圖片來源博客園c#技術漫談之垃圾回收(GC)

Garbage collection happens automatically when a request for memory cannot be satisfied using available free memory

 GC發生的時機,就是相應的堆達到了閾值,因為堆也有大小限制,並不是無限的。儘管2代堆或者大物件堆滿的時候,通過增加新的記憶體段來滿足記憶體分配,如果沒有可用的記憶體,這時就會報記憶體上限溢位。

四、GC不能釋放非托管資源

有兩種情況,第一種:托管代碼取用了非托管資源,比如檔案操作、資料庫連接、網絡連接等。這時候必須手動釋放,或實現 dispose樣式,或實現物件終結

When a type uses unmanaged resources that must be released before instances of the type are reclaimed, the type can implement a finalizer.

In most cases, finalizers are implemented by overriding the Object.Finalize method; however, types written in C# or C++ implement destructors, which compilers turn into an override of Object.Finalize

必須註意的一點是,實現物件終結器,GC會在釋放物件之前自動呼叫。其實這是一個代價非常高昂的備用機制。所以能自己釋放非托管資源的,就自己釋放。

如果一個物件中包含有終結器,那麼在new的時候放入到終結者佇列。當GC會把這個物件標為垃圾時,放入到另一個佇列F-Reachable中。這個佇列包含了所有帶有終結器並且將被作為垃圾收集的物件,這些物件的終結器都將被執行。在垃圾收集的過程總並不會執行終結器代碼。而是由.NET 行程的終結執行緒呼叫。因此,此時的垃圾回收滯後一段時間,目的在於等待終結器代碼執行的完成。

五、dispose樣式

這是基類和子類的dispose樣式,來源於官網。

赞(0)

分享創造快樂