- Global區域 -若變數,函式或物件宣告成static,將被配置到此區域內(這類的記憶體,常被稱為全域變數(global variable), 此區域內的所有資料為同一個process共用; Global區域內的記憶體生命週期會維持到程式結束後, 才會被釋放.
- Stack區域 - Stack區域是用來儲存區域變數(local variable), 記憶體配置的生命週期受到其區塊範圍 (block scope) 的影響,當進入區塊時,會自動配置記憶體, 離開此區塊,記憶體也會被釋放.
- Heap區域- Heap是儲存動態配置的資料(例如:使用new或malloc()宣告), 此記憶體不會隨函式結束被釋放. 而且釋放方式會因不同程式語言而不同. 對於C/C++這類的程式, 記憶體釋放需要由程式員手動釋放(例如: 使用delete 或 free()); 而 Java 與 C# 程式, 記憶體釋放則由 VM(虛擬機)自動作處理, 也就是Garbage Collection的機制.
GC 會追蹤 (tracing) 所有活著的物件, 如果不是活著的物件, 就會被標成 GC 該清除的垃圾.若變數為數值型別, 當變數超出有效範圍時, CLR會直接回收它在Stack上所佔用的空間, 若變數為參考型別, CLR會先回收它在Stack上佔用的空間, 再將Heap上的空間視為garbage, 等待GC回收. 若參考型別的變數在其有效範圍內重新初始化, 則原先所指向的物件亦會被視為garbage.
然而, 被視為garbage的物件, 並不會馬上就被GC回收, 而是根據GC內的演算法, 依變數被判定的generation而有不同. 下面為GC的測試程式:
C#程式碼 :
執行結果, GC會在程式結束後自動作記憶體回收:
然而, 被視為garbage的物件, 並不會馬上就被GC回收, 而是根據GC內的演算法, 依變數被判定的generation而有不同. 下面為GC的測試程式:
C#程式碼 :
static void Main(string[] args)
{
Console.WriteLine("程式開始");
MyClass myClass = new MyClass();
myClass = null;
Console.WriteLine("程式結束");
}
執行結果, GC會在程式結束後自動作記憶體回收:
[圖1] GC自動回收記憶體
雖然GC會幫程式人員自動清理記憶體, 但還是有可能發生, 要及時釋放某些記憶體(unmanged object)或資源時, 例如視窗控制代碼 (HWND), 資料庫連接等時, 該怎麼做?
C# 提供三種方式, 讓程式人員可手動釋放記憶體或資源: (1) 呼叫GC.Collect()
使用GC.Collect()可強制系統嘗試回收可用記憶體的最大量. 但一般來說. 應該盡量避免呼叫GC.Collect()來回收記憶體, 因為在記憶體回收行程執行回收之前,它會暫停所有目前正在執行的執行緒. 如果在不必要的情況下呼叫 GC.Collect()太過頻繁的話, 反而會造成效能上的問題. 此方法使用範例如下:
C#檔
C#檔
static void Main(string[] args)
{
Console.WriteLine("程式開始");
MyClass myClass = new MyClass();
myClass = null;
GC.Collect();
//因為GC回收時是在另一個thread
//所以使用WaitForPendingFinalizers等到確實回收後再繼續執行
GC.WaitForPendingFinalizers();
Console.WriteLine("程式結束");
}
執行結果
[圖2] 使用GC.Collect()強制回收記憶體
實作 Finalize 方法, 如同C++中的解構函式語法, 程式碼範例如下:
C#檔
public class MyClass
{
UnmanagedResource unmRes ;
public MyClass()
{
Console.WriteLine("建構 MyClass");
unmRes = new UnmanagedResource();
}
~MyClass()
{
/// 釋放unmanaged資源
unmRes.Release();
Console.WriteLine("解構 MyClass");
}
}
執行結果:
[圖3] 利用Finalize 回收記憶體
Finalize 雖可用於釋放一些記憶體, 但執行的時機由GC控制, 因此我們無法預期Finalize()究竟會何時會被呼叫, 這樣如果我們有急迫的資源必須釋放,這時候怎麼辦?
(3)使用Dispose
若要明確地釋放資源和物件, 使用Dispose將是最好的方式.程式員呼叫的方式如下:
C#檔:
執行結果:
Console.WriteLine("程式開始");
using (Memory memory = new Memory())
{
}
Console.WriteLine("程式結束");
[圖4] 利用Dispose回收記憶體
結 論 : - 明確地呼叫Dispose()
- 使用using陳述式, 在離開using區塊時, 其Dispose()會被明確地呼叫
C#檔:
public class Memory : IDisposable
{
private bool isDisposed = false;
public Memory()
{
Console.WriteLine("建構 Memory");
}
~Memory()
{
Dispose(false);
Console.WriteLine("解構 Memory");
}
protected void Dispose(bool Diposing)
{
if (!isDisposed)
{
if (Diposing)
{
//Clean Up managed resources
}
//Clean up unmanaged resources
}
isDisposed = true;
}
public void Dispose()
{
Console.WriteLine("Dispose called!");
Dispose(true);
//藉由呼叫 GC.SuppressFinalize 方法來避免 Finalize 方法的執行
GC.SuppressFinalize(this);
}
}
執行結果:
Console.WriteLine("程式開始");
using (Memory memory = new Memory())
{
}
Console.WriteLine("程式結束");
[圖4] 利用Dispose回收記憶體
- GC會自動管理Managed resource, 所以不需要程式員煩惱, 除非有用到 unmanaged resource 並需要釋放, 才會用Dispose/Finalize
- Finalize() 是交給 GC 安排時間去處理, Dispose() 則讓使用者決定使用的時機
- 除非必要時機, 否則不建議呼叫GC類別的Collect方法來強制回收記憶體
記憶體回收
.NET 的自動記憶體管理
C# garbage collection 筆記
.NET Framework記憶體回收機制
C# - 資源釋放的觀念整理
實作 Finalize 和 Dispose 以清除 Unmanaged 資源