2

私の質問は、悪夢のようなメモリ リークのデバッグに関するものです。
私のアプリには、から派生した単純なクラスがありTObjectます。そのクラスのすべてのオブジェクトは、から派生したクラスのコレクション/リストに格納されますTObjectList

type
  TOffer = class(TObject)
    Item: string;
    Price: string;
    Id: string;
  end;

  TOffers = class(TObjectList<TOffer>)
  protected
    procedure SetOffer(I: Integer; AOffer: TOffer);
    function GetOffer(I: Integer): TOffer;
  public
    property Offers[I: Integer]: TOffer read GetOffer write SetOffer
  end;

使用シナリオ:
クローラーはオファーをダウンロードして解析し、オブジェクト コレクションに保存します。このアプローチは、後でオブジェクトを参照できるため、非常に便利なようです (グリッド/リストを埋める、ファイルに書き込むなど)。

問題は、メモリ リークを回避するためのオブジェクトの適切な破棄です。アプリは開始時に最大 4Mb のメモリを割り当てますが、最大 12k のオファーを処理した後、32Mb を消費します。プロセスが終了した後、適切に破棄されていないオブジェクト/変数によって引き起こされるリーク。

ReportMemoryLeaksOnShutdown恐ろしい数字を示していますが、重要なのは-どこを見ればいいのか、どうすれば適切にデバッグできるのかわかりません。

別の例はvar MyString: string、適切な処分が必要な変数です!! それは私にとってちょっとした洞察でした:)各プロシージャ/関数は、スコープ外の変数のガベージコレクションを自動的に管理すると思いました。

オファーのリストは、次の関数によって作成されます。

function GetOffersList: TOffers;
begin
  Result := TOffers.Create;
  while not rs.EOF do
  begin
    Offer := TOffer.Create;
    try
       // here come collected offer attributes as variables of type string:
        Order.Item := CollectedOfferItem;
        Order.Price := CollectedOfferPrice;
        Order.Id := CollectedOfferId;
        Result.Add(Offer);
    finally
        Offer := nil;
    end;
  end;
end;

次に、これらのオファーをコレクションとして直接扱います。重要なことは、このアプリを 24 時間年中無休で実行することです。そのため、適切なリソースの処分が必須です。

  • 上記のタイプのオブジェクトを適切に処分する方法は?
  • オブジェクト/オブジェクト リストを管理するための他の手法を検討しますか?
  • タイプの変数を適切に破棄する方法はstring?
  • Delphi でのメモリ リークとの戦いに関する良い読み物を教えてください。

ありがとうございました。

4

2 に答える 2

8

デフォルトでは、オブジェクトを作成すると、その所有者になります。あなたが所有者である限り、あなたはそれを解放する責任があります。一般的なパターンのいくつかを次に示します。

1. ローカル変数

メソッドで作成され、ローカルでのみ参照されるオブジェクトの場合は、try/finally パターンを使用します。

Obj := TMyClass.Create;
try
  ... use Obj
finally
  Obj.Free;
end;

2. 別のオブジェクトが所有するオブジェクト

通常、コンストラクタで作成され、デストラクタで破棄されます。ここには、所有オブジェクトへの参照を保持する所有オブジェクトのメンバー フィールドがあります。必要なFreeのは、所有するクラスのデストラクタで所有されているすべてのオブジェクトを呼び出すことだけです。

3. 所有する TComponent

TComponentまたは派生クラスが で作成された場合Owner、その所有者はコンポーネントを破棄します。あなたはそれをする必要が無い。

4. OwnsObjects を True に設定した TObjectList など

質問でこのパターンを示します。を作成し、TObjectList<T>デフォルトOwnsObjectsではTrueです。これは、メンバーをコンテナに追加すると、コンテナが所有権を引き継ぐことを意味します。その時点から、コンテナーはそのメンバーを破棄する責任を負います。その必要はありません。ただし、誰かがコンテナーを破棄する必要があります。

5.参照カウントされたインターフェースオブジェクト

一般的な例は、 から派生したオブジェクトですTInterfacedObject。インターフェイス参照カウントは、有効期間を管理します。オブジェクトを破棄する必要はありません。

6. 新しいインスタンスを作成して返す関数

これは、スペクトルのよりトリッキーな終わりに向かっています。ありがたいことに、それはかなりまれなパターンです。この考え方は、関数が新しくインスタンス化され初期化されたオブジェクトを呼び出し元に返し、呼び出し元が所有権を引き継ぐというものです。ただし、関数がまだ実行されている間は、それが所有者であり、例外に対して防御する必要があります。通常、コードは次のようになります。

function CreateNewObject(...): TMyClass;
begin
  Result := TMyClass.Create;
  try
    Result.Initialize(...);
  except
    Result.Free;
    raise;
  end;
end;

Freeコードはfinallyを使用する立場にないため、これは呼び出しと再発生を伴う例外ハンドラーでなければなりません。呼び出し元はそれを行います:

Obj := CreateNewObject(...);
try
  ....
finally
  Obj.Free;
end;

質問のコードを見ると、リストの項目 4 と 6 の両方を使用しているようです。GetOffersListただし、 の実装は例外セーフではないことに注意してください。しかし、それが問題であるという兆候はありません。呼び出すコードがGetOffersListコンテナーの破棄に失敗していると思われます。

なぜ文字列を漏らしているのですか?文字列はマネージド オブジェクトです。それらは参照カウントされ、それらを破棄するために明示的なアクションを実行する必要はありません。ただし、それらが他のクラスに含まれている場合、そのインスタンスがリークされ、含まれている文字列もリークされます。したがって、オブジェクトのリークを修正することに専念すれば、文字列のリークに対処できます。

価値があるのは、TOffer私にとっては参照型というよりも値型のように感じます. メソッドはなく、3 つの単純なスカラー値が含まれています。レコードにして使ってみませんTList<TOffer>か?


それで、どのように進めますか?FastMM リーク レポートが必要です。縮小された Embarcadero バージョンではなく、完全な FastMM が必要になります。割り当て解除と一致しなかった割り当てを識別します。それらを1つずつ処理します。

これと並行して、良質なコードを勉強してください。優れたオープン ソースの Delphi ライブラリは、上記のすべてのパターンを示し、さらに多くのパターンを示します。彼らから学びましょう。

于 2014-04-01T16:07:15.110 に答える