[Delphi]Memory Leak 處理筆記

[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 的情況,則會出現以下的訊息

image

 

這樣要找問題就比較不會大海撈針,就可以針對 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 的處理,但是在迴圈中建立的物件,只有最後一次有被釋放,因此如果用這樣的方式去執行的話,則就會出現以下的錯誤訊息

image

 


後記 

使用 ReportMemoryLeaksOnShutdown 雖然是個不錯的方式,但他效果還是有限,對於像是第二個範例的一些隱藏性的問題,所造成的記憶體沒有釋放的狀況,就比較沒有幫助了。因此如果使用這樣的方式,再配合上好的開發習慣,像是在建立物件之後,後面馬上加上 Try … Finally … End 的處理,用完就馬上釋放,那這樣還是比較好的方法。