前陣子在工作上遇到了"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()),緩衝區被玩爆(這種問題很快會爆發,好查)
- 記憶體控制機制設計有問題
- 案例分析:瀑布式Textbox表現行數控制
分析記憶體洩漏的好工具,perfmon.exe
- 心得:
- 帶出的數據非常完整,而且可以設定固定採集時間長度,記錄在不同的採集檔內,比起自己在程式碼內寫LOG,用這工具比較能確保實驗在固定的資料搜集條件下進行
- 抓漏心法:
- 找出可能的嫌疑點,開perfmon設定長度N分鐘週期M秒採樣,時間到後觀察增長趨勢
- 沒改善,表示找錯方向,改動其他嫌疑點,重複上面過程,再看一遍
- 詳細使用方法:
- 非常好的評論:
- http://www.codeproject.com/Articles/42721/Best-Practices-No-Detecting-NET-application-memo#Avoidtaskmanagertodetectmemoryleak
- 摘要:
- 不要用Task Manager觀察是否洩漏,因為其表現的是總分配空間而非實際使用空間,且涵蓋了與其他行程共用的Shared Memory大小
- 應該還是用Perfmon.exe作為分析工具
- Private Byte增長:表示Unmanaged Code洩漏
- ByteInAllHeap增長:表示Managed Code洩漏
參考資料:
經常性的字串處理你該用StringBuilder
Using using as grantee (even for the filestream):
Close and Dispose
Heap、Stack、Reference Type、Value Type、Boxing、Unboxing
沒有留言:
張貼留言