98

私が取り組んでいるC#/。NETアプリケーションは、遅いメモリリークに悩まされています。私はSOSでCDBを使用して何が起こっているのかを判断しようとしましたが、データが意味をなさないように思われるので、あなたの1人が以前にこれを経験したことがあると期待していました。

アプリケーションは64ビットフレームワークで実行されています。データを継続的に計算してリモートホストにシリアル化し、ラージオブジェクトヒープ(LOH)にかなりの打撃を与えています。ただし、一時的なものと予想されるLOHオブジェクトのほとんどは、計算が完了してリモートホストに送信されたら、メモリを解放する必要があります。しかし、私が見ているのは、メモリの空きブロックでインターリーブされた多数の(ライブ)オブジェクト配列です。たとえば、LOHからランダムなセグメントを取得します。

0:000> !DumpHeap 000000005b5b1000  000000006351da10
         Address               MT     Size
...
000000005d4f92e0 0000064280c7c970 16147872
000000005e45f880 00000000001661d0  1901752 Free
000000005e62fd38 00000642788d8ba8     1056       <--
000000005e630158 00000000001661d0  5988848 Free
000000005ebe6348 00000642788d8ba8     1056
000000005ebe6768 00000000001661d0  6481336 Free
000000005f214d20 00000642788d8ba8     1056
000000005f215140 00000000001661d0  7346016 Free
000000005f9168a0 00000642788d8ba8     1056
000000005f916cc0 00000000001661d0  7611648 Free
00000000600591c0 00000642788d8ba8     1056
00000000600595e0 00000000001661d0   264808 Free
...

明らかに、私のアプリケーションが各計算中に長寿命で大きなオブジェクトを作成している場合は、これが当てはまると思います。(これを行い、ある程度のLOHフラグメンテーションがあることを受け入れますが、ここでは問題ではありません。)問題は、上記のダンプで確認できる非常に小さい(1056バイト)オブジェクト配列であり、コードでは確認できません。作成されており、何とか根付いたままです。

また、ヒープセグメントがダンプされたときに、CDBがタイプを報告しないことに注意してください。これが関連しているかどうかはわかりません。マークされた(<-)オブジェクトをダンプすると、CDB/SOSはそれを正常に報告します。

0:015> !DumpObj 000000005e62fd38
Name: System.Object[]
MethodTable: 00000642788d8ba8
EEClass: 00000642789d7660
Size: 1056(0x420) bytes
Array: Rank 1, Number of elements 128, Type CLASS
Element Type: System.Object
Fields:
None

オブジェクト配列の要素はすべて文字列であり、文字列はアプリケーションコードから認識できます。

また、!GCRootコマンドがハングして戻ってこないため、GCルートを見つけることができません(一晩放置しようとしたこともあります)。

したがって、これらの小さな(<85k)オブジェクト配列がLOHに配置される理由について誰かが光を当てることができれば、非常にありがたいです。.NETはどのような状況で小さなオブジェクト配列をそこに配置しますか?また、これらのオブジェクトのルートを確認する別の方法を知っている人はいますか?


アップデート1

昨日遅くに私が思いついたもう1つの理論は、これらのオブジェクト配列は最初は大きく、縮小されて、メモリダンプに明らかな空きメモリのブロックが残っているというものです。私が疑わしいのは、オブジェクト配列が常に1056バイトの長さ(128要素)、参照用に128 * 8、32バイトのオーバーヘッドであるように見えることです。

ライブラリまたはCLR内の安全でないコードが、配列ヘッダーの要素数フィールドを破損している可能性があります。私が知っているちょっとしたロングショット...


アップデート2

Brian Rasmussen(受け入れられた回答を参照)のおかげで、問題は文字列インターンテーブルによって引き起こされたLOHの断片化として識別されました!これを確認するためのクイックテストアプリケーションを作成しました。

static void Main()
{
    const int ITERATIONS = 100000;

    for (int index = 0; index < ITERATIONS; ++index)
    {
        string str = "NonInterned" + index;
        Console.Out.WriteLine(str);
    }

    Console.Out.WriteLine("Continue.");
    Console.In.ReadLine();

    for (int index = 0; index < ITERATIONS; ++index)
    {
        string str = string.Intern("Interned" + index);
        Console.Out.WriteLine(str);
    }

    Console.Out.WriteLine("Continue?");
    Console.In.ReadLine();
}

アプリケーションは最初に、ループ内の一意の文字列を作成して逆参照します。これは、このシナリオでメモリがリークしないことを証明するためだけのものです。明らかにそうすべきではなく、そうではありません。

2番目のループでは、一意の文字列が作成され、インターンされます。このアクションは、それらをインターンテーブルにルートします。私が気づかなかったのは、インターンテーブルがどのように表現されているかです。これは、LOHで作成された一連のページ(128個の文字列要素のオブジェクト配列)で構成されているようです。これはCDB/SOSでより明白です。

0:000> .loadby sos mscorwks
0:000> !EEHeap -gc
Number of GC Heaps: 1
generation 0 starts at 0x00f7a9b0
generation 1 starts at 0x00e79c3c
generation 2 starts at 0x00b21000
ephemeral segment allocation context: none
 segment    begin allocated     size
00b20000 00b21000  010029bc 0x004e19bc(5118396)
Large object heap starts at 0x01b21000
 segment    begin allocated     size
01b20000 01b21000  01b8ade0 0x00069de0(433632)
Total Size  0x54b79c(5552028)
------------------------------
GC Heap Size  0x54b79c(5552028)

LOHセグメントのダンプを取ると、リークしているアプリケーションで見たパターンが明らかになります。

0:000> !DumpHeap 01b21000 01b8ade0
...
01b8a120 793040bc      528
01b8a330 00175e88       16 Free
01b8a340 793040bc      528
01b8a550 00175e88       16 Free
01b8a560 793040bc      528
01b8a770 00175e88       16 Free
01b8a780 793040bc      528
01b8a990 00175e88       16 Free
01b8a9a0 793040bc      528
01b8abb0 00175e88       16 Free
01b8abc0 793040bc      528
01b8add0 00175e88       16 Free    total 1568 objects
Statistics:
      MT    Count    TotalSize Class Name
00175e88      784        12544      Free
793040bc      784       421088 System.Object[]
Total 1568 objects

ワークステーションが32ビットで、アプリケーションサーバーが64ビットであるため、オブジェクト配列のサイズは(1056ではなく)528であることに注意してください。オブジェクト配列はまだ128要素の長さです。

したがって、この話の教訓は、非常に慎重なインターンを行うことです。インターンしている文字列が有限集合のメンバーであることがわからない場合、少なくともCLRのバージョン2では、LOHの断片化が原因でアプリケーションがリークします。

私たちのアプリケーションの場合、逆シリアル化コードパスに、非マーシャリング中にエンティティ識別子をインターンする一般的なコードがあります。これが原因であると強く疑っています。ただし、同じエンティティが複数回逆シリアル化された場合に、識別子文字列の1つのインスタンスのみがメモリに保持されるようにしたかったので、開発者の意図は明らかに良かったです。

4

7 に答える 7

2

GCがどのように機能するか、および長寿命のオブジェクトが第2世代でどのように終了するかについての説明を読むと、LOHオブジェクトの収集は完全な収集でのみ行われます-第2世代の収集と同様に、頭に浮かぶアイデアです。 ..第2世代と大きなオブジェクトは一緒に収集されるので、同じヒープに保持しないのはなぜですか?

それが実際に起こっていることである場合、それは、小さなオブジェクトがどのようにLOHと同じ場所に配置されるかを説明します-それらが第2世代で終わるのに十分な長さである場合。

したがって、あなたの問題は、私が思いついたアイデアに対するかなり良い反論のように見えます。それは、LOHの断片化につながるでしょう。

要約:あなたの問題は、同じヒープ領域を共有するLOHと第2世代によって説明される可能性がありますが、これが説明であることを証明するものではありません。

更新:の出力は、!dumpheap -statこの理論を水から吹き飛ばします!第2世代とLOHには独自のリージョンがあります。

于 2009-03-26T19:35:50.977 に答える
1

ここでは、LOH割り当ての正確なコール スタックを特定する方法をいくつか紹介します。

また、LOH フラグメンテーションを回避するには、オブジェクトの大規模な配列を事前に割り当てて固定します。必要に応じてこれらのオブジェクトを再利用してください。LOH フラグメンテーションに関する投稿はこちらです。このようなことは、LOH フラグメンテーションを回避するのに役立ちます。

于 2010-04-04T01:16:05.967 に答える
1

形式がアプリケーションとして認識できる場合、この文字列形式を生成しているコードを特定していないのはなぜですか? いくつかの可能性がある場合は、固有のデータを追加して、どのコード パスが原因であるかを突き止めてみてください。

配列が解放された大きなアイテムと交互に配置されているという事実は、それらが元々ペアになっていたか、少なくとも関連していたと推測します。解放されたオブジェクトを特定して、それらを生成していたものと関連する文字列を特定してください。

これらの文字列を生成しているものを特定したら、何が GC を妨げているのかを突き止めてください。おそらく、それらは、ログ記録などのために、忘れられた、または使用されていないリストに詰め込まれています。


EDIT:メモリ領域と特定の配列サイズを今のところ無視してください。これらの文字列で何が行われてリークを引き起こしているのかを理解してください。プログラムがこれらの文字列を 1 回か 2 回作成または操作しただけで、トレースするオブジェクトが少ない場合は、!GCRoot を試してください。

于 2009-03-26T18:36:53.427 に答える
1

素晴らしい質問です。質問を読んで学びました。

逆シリアル化コード パスの他のビットも大きなオブジェクト ヒープを使用しているため、断片化が発生していると思います。すべての弦が同時に抑留されていれば、大丈夫だと思います。

.net ガベージ コレクターがどれほど優れているかを考えると、逆シリアル化コード パスに通常の文字列オブジェクトを作成させるだけで十分な可能性があります。必要性が証明されるまで、これ以上複雑なことはしないでください。

せいぜい、あなたが見た最後のいくつかの文字列のハッシュテーブルを保持し、これらを再利用することを検討します. ハッシュ テーブルのサイズを制限し、テーブルの作成時にサイズを渡すことで、ほとんどの断片化を止めることができます。次に、最近表示されていない文字列をハッシュ テーブルから削除して、サイズを制限する方法が必要です。 しかし、デシリアライゼーション コード パスが作成する文字列がとにかく短命である場合、どちらかといえばあまり得られません。

于 2010-01-15T11:05:59.023 に答える