2016年1月10日 星期日

[.NET] Memory Leaks


前陣子在工作上遇到了"OutOfMemory"的例外,深感驚訝,以為在GC環境下,這種像是慢性病的東西可以從此與我絕緣,真要下手查BUG時,突然理解到,關於GC,關於記憶體分配模型,我理解的太模糊,才會感覺每一行指令好像都值得懷疑似的(最後發現是TextBox內容控制機制有問題)

.NET的記憶體分配模型:


  • 堆疊(Stack):
    • 何時被分配?
      • 進入函式,配置實值物件,或宣告實例參考時
      • 依序連續分配,像是盤子一層層疊起來所以稱為Stack
    • 什麼東西會被分配?
      • 實值型別(Value Type)物件
        • 註:可以從物件是否繼承自System.ValueType來確定是否為實值型別
        • 例如:Int , Float , 使用Structure宣告的物件
      • 參考型別(Reference Type)參考(Reference),可稱為Handle,概念上可以想成指標
    • 何時被回收?
      • 當函式退出(Return)時,按照分配順序一層層移除,後進先出
      • 註:所以若在此函式範圍做了 Object a = new Object,在退出此函式時參考a會被沒收,此時跟a鏈結的物件,就成了無主遊魂,很快地會被牛頭馬面GC(Garbage Collector)回收,帶到陰曹地府等待投胎轉世(輪迴?)

  • 託管堆積(Managed Heap):
    • 何時被分配?
      • 隨時隨地,在你使用New配置物件時
    • 什麼東西會被分配?
      • 參考型別物件,可稱為Instance
        • 註:String是參考型別物件,分配在堆積上,只是其屬性為不可修改(Immutable)
    • 何時會被回收?
      • 當GC掃描到此Instance並無任何參考指向他,便會標記起來,接著自動回收

  • 非託管堆積(Unmanaged Heap):
    • 何謂非託管?
      • 就是非在CLR上執行的程式,不受.NET管理,好比你用C語言基於Standard Library寫出來的程式
    • 何時被分配?
      • 隨時隨地,在你使用New/malloc配置物件時
    • 什麼東西會被分配?
      • 任何你用New/malloc配置的東西
    • 何時會被回收?
      • 必須手動明確(Explicit)地呼叫Delete/Free,此物件所佔據的記憶體空間才會被釋放

可能的記憶體洩漏原因:

  • 有問題的非託管程式碼
    • 物件被分配(New/malloc)出來了,但忘記寫下Delete(Free),典型的錯誤
    • 最可怕的是第三方供應商的函式庫有問題,你沒程式碼他又不想理你
  • 託管程式碼:
    • 對於IDisposable物件,使用過後未呼叫Dispose()
      • 非託管程式碼或是運用到獨佔資源(Resource)的物件可能會實作此介面,作為提供呼叫端明確呼叫釋放時機的可能性
      • 例如:Filestream、SqlConnection、Usercontrol
      • 需要手動控制釋放時機的物件才實做IDisposable(程式結束時釋放的那種不算)
      • 對於所有IDisposable物件,盡量使用Using,減少人為失誤造成的困擾
    • 物件已失去參考,但隸屬的EventHandler仍與活動中的Event連結
      • 由於成員函式隱含實例作為參考,可能會使得EventHandler對應的物件無法被釋放(這點相當有趣,物件成員函式預設至少有一引數為自己)
    • 其他,自己應用上的失誤
      • Queue:Enqueue沒有Dequeue的快,結果就是一直累積
      • 傾盡CPU全力,狂寫檔案(StreamWriter.Write()),緩衝區被玩爆(這種問題很快會爆發,好查)
      • 記憶體控制機制設計有問題

分析記憶體洩漏的好工具,perfmon.exe

    • 心得:
      • 帶出的數據非常完整,而且可以設定固定採集時間長度,記錄在不同的採集檔內,比起自己在程式碼內寫LOG,用這工具比較能確保實驗在固定的資料搜集條件下進行
    • 抓漏心法:
      • 找出可能的嫌疑點,開perfmon設定長度N分鐘週期M秒採樣,時間到後觀察增長趨勢
      • 沒改善,表示找錯方向,改動其他嫌疑點,重複上面過程,再看一遍
    • 詳細使用方法:
    • 非常好的評論:


參考資料:
經常性的字串處理你該用StringBuilder
Using using as grantee (even for the filestream):
Close and Dispose
Heap、Stack、Reference Type、Value Type、Boxing、Unboxing

沒有留言:

張貼留言