[Delphi]Memory Leak 處理筆記
這一陣子專案要準備結束,因此正式進入壓力測試的階段,因此我們就錄製了一些鍵盤的代碼,寫了一段程式去一值模擬系統做各種處理,來看當經過長時間的運作之後是否有任何異常,確保程式的品質。但是在經過連續一天不間斷的運作之後,便發覺到程式占用記憶體的狀況越來越多,導致到系統的反應也就越來越慢,而以往面對這樣的問題,我們多半都是把程式一個一個切開來,每一段進行壓力測試和 Code Review,慢慢地來找出問題的所在。
而在此次的過程中,專案同仁找到另外一個方式來做處理,原來在 Delphi 2006 之後,有多加了一個 「ReportMemoryLeaksOnShutdown」的全域變數,當我們在應用程式內加這個屬性設定為 True 之後,當關閉應用程式的時候,如果應用程式有發生沒有釋放的記憶體的狀況,那就會有個視窗顯示沒有釋放的物件和記憶體的大小。
為什麼會有 Memory Leak 的狀況呢 ? 基本上就是你有使用到一塊的記憶體,當沒有需要使用的時候,卻沒有人將那塊記憶體歸還給 OS,導致到最後要結束整個應用程式的時候,才全部歸還給 OS。但又有朋友在討論過程中反映說,目前我們在使用 Delphi,正常來說都是對物件的操作,這是不是 Delphi 忘了對物件做釋放啊 ? 針對這些問題以下我拿一些 Sample Code 來作範例說明一下
這個是比較典型會看到的
procedure TForm1.Button2Click(Sender: TObject); var mList : TStrings ; begin mList := TStringList.Create ; mList.Add('A'); mList.Add('B'); end;
因此如果當有這樣的狀況發生的時候,在我們離開應用程式的時候,如果有設定 ReportMemoryLeaksOnShutdown 為 Ture 的情況,則會出現以下的訊息
這樣要找問題就比較不會大海撈針,就可以針對 TStringList 來查看問題了。而這樣的工具還是會有個限制,在我們測試的過程中,有找到一個特殊的狀況,可參考下面的程式碼
procedure TForm1.Button3Click(Sender: TObject); var mAdo : TAdoDataset ; begin mAdo := TAdoDataset.Create(Self); mAdo.Connection := Self.ADOConnection1 ; mAdo.CommandText:= 'select 1'; mAdo.Open ; end;
稍微有寫過 Delphi 的人都應該看得出來,這個也是很典型沒有釋放的狀況,但是當程式執行完畢的時候,卻沒有出現任何訊息 ?! 這個最主要的原因是物件的 Owner 當結束的時候,會把所有屬於他的物件都釋放,因此上面這段程式碼中,我們建立 ADODataset 的物件時候,指定的 Owner 是 Form1 ( 也就是 Self ),因此當作業結束的時候,Application 會去釋放 Form1 , 此時 Form1 會把那個沒有釋放的給都釋放之後,才會去把自己給 Free,因此我們如果調整一下程式碼再做個測試
procedure TForm1.Button1Click(Sender: TObject); var mPtr : Integer ; mAdo : TAdoDataset ; begin for mPtr := 1 to 10 do begin mAdo := TAdoDataset.Create(nil); mAdo.Connection := Self.ADOConnection1 ; mAdo.CommandText:= 'select 1'; mAdo.Open ; end; mAdo.Free ; end;
像這段程式碼中我們調整將 Owner 指定為 Nil,雖然看起來最後有做 Free 的處理,但是在迴圈中建立的物件,只有最後一次有被釋放,因此如果用這樣的方式去執行的話,則就會出現以下的錯誤訊息
後記
使用 ReportMemoryLeaksOnShutdown 雖然是個不錯的方式,但他效果還是有限,對於像是第二個範例的一些隱藏性的問題,所造成的記憶體沒有釋放的狀況,就比較沒有幫助了。因此如果使用這樣的方式,再配合上好的開發習慣,像是在建立物件之後,後面馬上加上 Try … Finally … End 的處理,用完就馬上釋放,那這樣還是比較好的方法。