1. 程式人生 > >.NET資源洩露與處理方案

.NET資源洩露與處理方案

.NET雖然擁有強大易用的垃圾回收機制,但並不是因為這樣,你就可以對資源管理放任不管,其實在稍不注意的時候,可能就造成了資源洩露,甚至因此導致系統崩潰,到那時再來排查問題就已經是困難重重。

一、知識點簡單介紹

常見的資源洩露有:

  • 記憶體洩漏:非託管資源沒有釋放、非靜態物件註冊了靜態例項。
  • GDI洩露:字型。
  • 控制代碼洩露:Socket或執行緒。
  • 使用者物件洩露:移除的物件未釋放。

二、具體例項

1. 記憶體洩漏

很常見的現象是分不清哪些物件需要釋放,對於控制元件、Stream等一些非託管資源也只管新增,卻沒有釋放,功能是實現了,卻埋了顆不小的雷。

private void button1_Click(object sender, EventArgs e)
{
    for(int i=0;i<1000;i++)
        this.Controls.Add(new TabPage());
}
private void button1_Click(object sender, EventArgs e)
{
    new Form2.ShowDialog();
}

如果你覺得寫這樣的程式碼很Cool,很簡潔,你在專案中也有這麼寫程式碼,那你就碰到大麻煩了,你試試在上面Form2中開個大一點的陣列來檢查記憶體,然後執行,按幾下按鈕,你就會發現,記憶體一直增加,即使你呼叫了GC也無濟於事。所以,對於此類非託管資源要記住釋放,用完即廢可以採用using關鍵字。

public Form2()
{
    InitializeComponent();
    MyApp.FormChanged += FormChanged;
}

上面這個例子中,MyApp是一個靜態類,如果在例項物件中向這種類裡面註冊了事件,而又沒有取消註冊,這樣也會遇到大麻煩,即使在外部已經記得呼叫了Form2的Dispose也是沒用的。

解決方案

  • 注意託管資源和非託管資源的釋放區別,非託管資源是需要手動釋放的。
  • 使用using關鍵字,避免忘記Dispose的情況,如上面的ShowDialog問題。(using中還起到了try-catch的作用,避免由於異常未呼叫Dispose的情況)
  • 使用UnLoad事件或者解構函式,對註冊的全域性事件進行取消註冊。
  • 特別注意自定義元件的穩定性更重要,發生問題時影響也更廣。注意繼承IDisposable介面,進行資源釋放

2. GDI洩露

一般會跟字型相關,例如我曾在Android上用Cocos2d做一個小遊戲時頻繁地切換字型、Dev控制元件的Font屬性賦值也會有這種現象。

XXX.Font = new Font(...)

解決方案

  • 這個問題我目前是採用字型池來解決,類似執行緒池的概念,相同Key值取同一個物件。若有更好方案歡迎留言討論

3. 控制代碼洩露

一般跟Socket和Thread(執行緒)有關

for(int i=0;i<1000;i++){
    new Thread(()=>{
        Thread.Sleep(1000);
    }).Start();
}

解決方案

  • Socket的場景暫時沒遇到。
  • 執行緒問題採用執行緒池相關的輔助類能有效解決,例如ThreadPool、Task、Parallel。

4. 使用者物件洩露

一般跟移除的物件未釋放有關

private void button1_Click(object sender, EventArgs e)
{
    tab.Remove(tabPage);
}

三、最後特別奉送一個記憶體釋放的大招

[DllImport("kernel32.dll", EntryPoint = "SetProcessWorkingSetSize")]
public static extern int SetProcessWorkingSetSize(IntPtr process, int minSize, int maxSize);
/// <summary>    
/// 釋放記憶體    
/// </summary>    
public static void ClearMemory()
{
    GC.Collect();
    GC.WaitForPendingFinalizers();
    if (Environment.OSVersion.Platform == PlatformID.Win32NT)
    {
        SetProcessWorkingSetSize(System.Diagnostics.Process.GetCurrentProcess().Handle, -1, -1);
    }
}

呼叫以上API能讓你的記憶體一下爆減,是不是很給力,一呼叫記憶體就降下來了。But,先別高興太早,這其實是偽釋放,只是暫時解決記憶體大量洩漏導致系統崩潰的應急處理方案。具體原因參考:SetProcessWorkingSetSize函式的騙局,關鍵資訊:實體記憶體轉虛擬記憶體,涉及磁碟讀寫。好處壞處都貼出來了,是否需要使用請君自己斟酌。

四、總結

實際上由於各個開發人員的水平跟接觸面不同,又沒有經過統一的培訓(各個人對資源釋放的理解與關注度不同,或者寫程式碼時就沒考慮記憶體未被釋放這種問題),發現問題的時候專案往往已經做到了一個階段,系統也比較龐大了,這種時候才發現記憶體洩露的問題確實是很頭疼的。

  • 資源洩露的場景往往是相互關聯的,發生最多的就是記憶體洩漏,而除了寫法可能有問題外,也可能是因為控制代碼洩露或使用者物件洩露引起的。

五、參考資料

  • C#記憶體洩露與資源釋放 經驗總結
  • 字型池技術實現
  • .NET多執行緒知識快速學習