25

Active Directoryで多くのクエリを頻繁に実行する必要がある、実行時間の長いプロセスがあります。この目的のために、DirectorySearcherクラスとDirectoryEntryクラスを使用してSystem.DirectoryServices名前空間を使用しています。アプリケーションのメモリリークに気づきました。

このコードで再現できます:

while (true)
{
    using (var de = new DirectoryEntry("LDAP://hostname", "user", "pass"))
    {
        using (var mySearcher = new DirectorySearcher(de))
        {
            mySearcher.Filter = "(objectClass=domain)";
            using (SearchResultCollection src = mySearcher.FindAll())
            {
            }            
         }
    }
}

これらのクラスのドキュメントには、Dispose()が呼び出されない場合にメモリリークが発生すると記載されています。私も処分せずに試しましたが、その場合はより多くのメモリがリークします。フレームワークバージョン2.0と4.0の両方でこれをテストしました。これまでに誰かがこれに遭遇したことがありますか?回避策はありますか?

更新:別のAppDomainでコードを実行しようとしましたが、どちらも役に立たなかったようです。

4

5 に答える 5

16

奇妙なことに、メモリリークは、検索結果を何もしなかった場合にのみ発生するようです。質問のコードを次のように変更しても、メモリはリークされません。

using (var src = mySearcher.FindAll())
{
   var enumerator = src.GetEnumerator();
   enumerator.MoveNext();
}

これは、内部のsearchObjectフィールドがレイジー初期化されており、Reflectorを使用してSearchResultCollectionを確認していることが原因のようです。

internal UnsafeNativeMethods.IDirectorySearch SearchObject
{
    get
    {
        if (this.searchObject == null)
        {
            this.searchObject = (UnsafeNativeMethods.IDirectorySearch) this.rootEntry.AdsObject;
        }
        return this.searchObject;
    }
}

searchObjectが初期化されない限り、disposeはアンマネージハンドルを閉じません。

protected virtual void Dispose(bool disposing)
{
    if (!this.disposed)
    {
        if (((this.handle != IntPtr.Zero) && (this.searchObject != null)) && disposing)
        {
            this.searchObject.CloseSearchHandle(this.handle);
            this.handle = IntPtr.Zero;
        }
    ..
   }
}

ResultsEnumeratorでMoveNextを呼び出すと、コレクションでSearchObjectが呼び出されるため、コレクションも適切に破棄されます。

public bool MoveNext()
{
  ..
  int firstRow = this.results.SearchObject.GetFirstRow(this.results.Handle);
  ..
}

私のアプリケーションのリークは、他の管理されていないバッファが適切に解放されなかったことが原因であり、私が行ったテストは誤解を招くものでした。この問題は現在解決されています。

于 2011-05-23T14:22:53.607 に答える
7

マネージラッパーは実際には何もリークしません。未使用のリソースを呼び出さない場合Disposeでも、ガベージコレクション中に再利用されます。

ただし、マネージコードはCOMベースのADSI APIのラッパーであり、基になるコードを作成すると、関数DirectoryEntryが呼び出されます。返されたCOMオブジェクトは、破棄されたとき、またはファイナライズ中に解放されます。ADsOpenObjectDirectoryEntry

ADsOpenObject APIを一連のクレデンシャルおよびWinNTプロバイダーと一緒に使用すると、メモリリークが文書化されます

  • このメモリリークは、Windows XP、Windows Server 2003、Windows Vista、Windows Server 2008、Windows 7、およびWindows Server2008R2のすべてのバージョンで発生します。
  • このメモリリークは、WinNTプロバイダーを資格情報と一緒に使用する場合にのみ発生します。LDAPプロバイダーは、この方法でメモリをリークしません。

ただし、リークはわずか8バイトであり、私が見る限り、WinNTプロバイダーではなくLDAPプロバイダーを使用しています。

呼び出すDirectorySearcher.FindAllと、かなりのクリーンアップが必要な検索が実行されます。このクリーンアップはで行われDirectorySearcher.Disposeます。コードでは、このクリーンアップは、ガベージコレクション中ではなく、ループの各反復で実行されます。

LDAP ADSI APIに文書化されていないメモリリークが実際にない限り、私が思いつくことができる唯一の説明は、アンマネージヒープの断片化です。ADSI APIはインプロセスCOMサーバーによって実装され、検索ごとにプロセスのアンマネージヒープにメモリが割り当てられる可能性があります。このメモリが断片化すると、新しい検索にスペースが割り当てられるときにヒープが大きくなる可能性があります。

私の仮説が正しい場合、1つのオプションは、別のAppDomainで検索を実行し、それを再利用してADSIをアンロードし、メモリをリサイクルすることです。ただし、メモリの断片化によってアンマネージメモリの需要が増える可能性があるとしても、必要なアンマネージメモリの量には上限があると思います。もちろん漏れがない限り。

DirectorySearcher.CacheResultsまた、プロパティで遊んでみることができます。false漏れを取り除くように設定していますか?

于 2011-05-09T11:45:32.943 に答える
3

実装上の制限により、SearchResultCollectionクラスは、ガベージコレクションされたときに、管理されていないすべてのリソースを解放できません。メモリリークを防ぐには、SearchResultCollectionオブジェクトが不要になったときにDisposeメソッドを呼び出す必要があります。

http://msdn.microsoft.com/en-us/library/system.directoryservices.directorysearcher.findall.aspx

編集:

perfmonを使用して明らかなリークを再現し、テストアプリのプロセス名にPrivate Bytesのカウンターを追加することができました(Experiments.vshost for me)

Private Bytesカウンターは、アプリのループ中に着実に増加し、約40,000,000から始まり、数秒ごとに約100万バイトずつ増加します。良いニュースは、アプリを終了するとカウンターが通常(35,237,888)に戻るため、最終的に何らかのクリーンアップが行われることです。

漏れたときのperfmonのスクリーンショットを添付しましたメモリリークのperfmonスクリーンショット

アップデート:

DirectoryServerオブジェクトのキャッシュを無効にするなど、いくつかの回避策を試しましたが、役に立ちませんでした。

FindOne()コマンドはメモリをリークしませんが、そのオプションを機能させるために何をしなければならないかわかりません。おそらく、ADコントローラーでは、フィルターを常に編集しているので、ドメインは1つだけです。 findallとfindoneは同じ結果になります。

また、10,000個のスレッドプールワーカーをキューに入れて、同じDirectorySearcher.FindAll()を作成しようとしました。それはかなり速く終了しましたが、それでもメモリリークが発生し、実際にはプライベートバイトは「通常の」リークの48MBではなく、約80MBになりました。

したがって、この問題では、FindOne()を機能させることができれば、回避策があります。幸運を!

于 2011-05-09T11:06:32.653 に答える
2

試したことはusingありDispose()ますか?ここからの情報

アップデート

使用が終了する前に電話してみてくださいde.Close();

申し訳ありませんが、これをテストするためのアクティブドメインサービスは実際にはありません。

于 2011-04-12T08:10:16.037 に答える
0

これを回避するための迅速で汚い方法を見つけました。

プログラムで同様の問題が発生しましたが、.GetDirectoryEntry()。Properties( "cn")。Valueを次のように変更しました。

.getDirectoryEntry()。Properties( "cn")。Value.ToStringを使用して、.valueがnullでないことを確認します。

私はGC.Collectにforeachの一時的な値を取り除くように指示することができました。.valueは、オブジェクトを収集するのではなく、実際にオブジェクトを存続させていたようです。

于 2013-09-16T20:07:10.650 に答える