15

SOAPを介して多数のサービスと通信し、数日間実行した後にOutOfMemoryExceptionがスローされるWCFクライアントアプリケーション(私は作成しておらず、まだあまり詳しくない)を分析すると、.netのPooledBufferManagerがアプリのメモリが不足し、OOMEが発生した場合でも、未使用のバッファを解放することはありません。

これはもちろん仕様に準拠しています:http://msdn.microsoft.com/en-us/library/ms405814.aspx

バッファプールがガベージコレクションによって再利用されると、プールとそのバッファは[...]破棄されます。

以下の質問の1つだけに自由に答えてください。私にはたくさんの質問があり、いくつかはより一般的な性質であり、いくつかはアプリでのBufferManagerの使用に固有のものです。

(デフォルトのプールされた)BufferManagerに関する最初のいくつかの一般的な質問:

1) GCがある環境では、OOMEにつながる場合でも、未使用のメモリを保持するBufferManagerが必要なのはなぜですか?私は知っています、BufferManager.Clear()があります。これを使用して、すべてのバッファーを手動で取り除くことができます。つまり、BufferManagerにアクセスできる場合です。私がアクセスできないように見える理由については、さらに下を参照してください。

2)「このプロセスは、バッファを使用する必要があるたびにバッファを作成および破棄するよりもはるかに高速です」というMSの主張にもかかわらず、GC(およびそのLOHなど)に任せて最適化するべきではありません。代わりにGC?

3) BufferManager.Take(33 * 1024 * 1024)を実行すると、64Mのバッファーが取得されます。これは、PooledBufferManagerが後で再利用するためにそのバッファーをキャッシュするためです。これは、私の場合はそうではないため、純粋なメモリの浪費-たとえば、34M、50M、または64Mが必要です。それで、HttpsChannelFactoryによって使用される(デフォルトでは、私が推測する)このような潜在的に非常に無駄なBufferManagerを作成することは賢明でしたか?特に、アプリケーションが10秒TOPSごと、通常はさらに数秒または数分ごとに通信するWCFおよびネットワークサービスについて話している場合、メモリ割り当てのパフォーマンスがどのように重要であるかがわかりません。

ここで、アプリケーションでのBufferManagerの使用に関連するいくつかのより具体的な質問があります。このアプリは、いくつかの異なるWCFサービスに接続します。それらのそれぞれについて、接続が同時に発生する可能性があるため、http接続用の接続プールを維持します。

1つのヒープダンプ内の単一の最大オブジェクトを検査します。これは、初期化時にアプリで1回だけ使用され、その後は必要ない64Mバイトの配列です。これは、サービスからの応答が初期化時にのみ大きいためです。これは、最適化(ディスクへのキャッシュなど)の対象となる可能性がありますが、私が使用した多くのアプリケーションでは一般的です。WinDbgでのGCルート分析により、次の結果が得られます(独自のクラスの名前を「MyServiceX」などにサニタイズしました)。

0:000:x86> !gcroot -nostacks 193e1000
DOMAIN(00B8CCD0):HANDLE(Pinned):4d1330:Root:0e5b9c50(System.Object[])->
035064f0(MyServiceManager)->
0382191c(MyHttpConnectionPool`1[[MyServiceX, MyLib]])->
03821988(System.Collections.Generic.Queue`1[[MyServiceX, MyLib]])->
038219a8(System.Object[])->
039c05b4(System.Runtime.Remoting.Proxies.__TransparentProxy)->
039c0578(System.ServiceModel.Channels.ServiceChannelProxy)->
039c0494(System.ServiceModel.Channels.ServiceChannel)->
039bee30(System.ServiceModel.Channels.ServiceChannelFactory+ServiceChannelFactoryOverRequest)->
039beea4(System.ServiceModel.Channels.HttpsChannelFactory)->
039bf2c0(System.ServiceModel.Channels.BufferManager+PooledBufferManager)->
039c02f4(System.Object[])->
039bff24(System.ServiceModel.Channels.BufferManager+PooledBufferManager+BufferPool)->
039bff44(System.ServiceModel.SynchronizedPool`1[[System.Byte[], mscorlib]])->
039bffa0(System.ServiceModel.SynchronizedPool`1+GlobalPool[[System.Byte[], mscorlib]])->
039bffb0(System.Collections.Generic.Stack`1[[System.Byte[], mscorlib]])->
12bda2bc(System.Byte[][])->
193e1000(System.Byte[])

BufferManagerによって管理される他のバイト配列のgcルートを見ると、他のサービス('MyServiceX'ではない)が異なるBufferPoolインスタンスを持っていることがわかります。したがって、それぞれが独自のメモリを浪費しており、無駄を共有していません。

4)ここで何か間違ったことをしていますか?私は決してWCFの専門家ではないので、さまざまなHttpsChannelFactoryインスタンスがすべて同じBufferManagerを使用するようにすることはできますか?

5)または、さらに良いのは、すべてのHttpsChannelFactoryインスタンスにBufferManagerをまったく使用しないように指示し、GCに「メモリの管理」というひどい仕事をするように依頼することでしょうか。

6)質問4)と5)に答えられない場合、すべてのHttpsChannelFactoryインスタンスのBufferManagerにアクセスし、それらに対して手動で.Clear()を呼び出すことができますか?これは最適なソリューションにはほど遠いですが、すでに役立ちます、私の場合、前述の64Mだけでなく、64M + 32M + 16M + 8M + 4M +2Mを1つのサービスインスタンスで解放します。それだけで、メモリの問題が発生することなくアプリが長持ちします(いいえ、BufferManager以外にメモリリークの問題はありませんが、コース中に大量のメモリを消費し、大量のデータを蓄積します何日もかかりますが、それはここでは問題ではありません)

4

3 に答える 3

11

私はあなたの質問#5に答えがあると信じています:

5)または、さらに良いのは、すべてのHttpsChannelFactoryインスタンスにBufferManagerをまったく使用しないように指示し、GCに「メモリの管理」というひどい仕事をするように依頼することでしょうか。

BufferManagerのバッファーの最大サイズを制御するMaxBufferPoolSizeバインディングパラメーターがあります。これを0に設定すると、バッファリングが無効になり、プールされたバッファの代わりにGCBufferManagerが作成されます。また、質問のように、メッセージが処理されるとすぐにGCがバッファを割り当てます。

この記事では、WCFメモリバッファ管理について詳しく説明します。

于 2013-06-07T12:50:43.907 に答える
10

4)ここで何か間違ったことをしていますか?私は決してWCFの専門家ではないので、さまざまなHttpsChannelFactoryインスタンスがすべて同じBufferManagerを使用するようにすることはできますか?

5)または、さらに良いのは、すべてのHttpsChannelFactoryインスタンスにBufferManagerをまったく使用しないように指示し、GCに「メモリの管理」というひどい仕事をするように依頼することでしょうか。

これらの2つの質問に対処する1つの方法は、TransferModeを「buffered」から「streamed」に変更することだと思います。「ストリーミング」モードにはいくつかの制限があり、使用できない可能性があるため、調査する必要があります。

更新:実際にはうまく機能します!アプリの起動時のバッファモードでのメモリ消費量は、ピーク時には630Mでしたが、完全に読み込まれると470Mに減少しました。ストリームモードに切り替えた後、メモリ消費量は一時的なピークを示さず、完全にロードされたとき、消費量はわずか270Mです。

ところで、これは私にとってクライアントアプリコードの1行の変更でした。私はこの行を追加する必要がありました:

httpsTransportBindingElement.TransferMode = TransferMode.StreamedResponse;
于 2011-08-31T05:55:50.120 に答える
4

ジョンが言うように、エッセイを書くよりも、1つの質問だけに答える方が簡単でしょう。しかし、これが私が思うことです

1)GCがある環境で、なぜBufferManagerが必要になるのでしょうか。

あなたはGCとバッファの概念を誤解しているようです。GCは、参照型のオブジェクトを処理し、オブジェクトがグラフの頂点(ポイントまたはノード)であり、他の頂点への有効なエッジ(線または接続)がないことを検出すると、メモリを解放します。バッファは、生データの一時的な配列のための一時的なストレージにすぎません。たとえば、WCFアプリケーションレベルのメッセージを送信する必要があり、その現在のサイズがトランスポートレベルのメッセージサイズよりも大きい場合、WCFはいくつかのトランスポートメッセージでそれを行います。レシーバーのサイズでは、WCFは完全なアプリケーションレベルのメッセージが到着するまで待機し、その後、処理のためにメッセージを渡します(ストリーミングバインディングでない場合)。一時的なトランスポートメッセージはバッファリングされます-受信側のメモリのどこかに保存されます。この例で新しいメッセージ用の新しいバッファーを作成すると非常に拡張性が高くなる可能性があるため、.NETは、バッファーのプールと共有を担当するバッファー管理クラスを提供します。

2)「このプロセスは、バッファを使用する必要があるたびにバッファを作成および破棄するよりもはるかに高速です」というMSの主張にもかかわらず、GC(およびそのLOHなど)に任せて最適化するべきではありません。代わりにGC?

いいえ、すべきではありません。バッファーとGCには共通点はありません(サンプルのコンテキストで毎回バッファーを破棄したい場合を除き、これは設計上の欠陥です)。彼らにはさまざまな責任があり、さまざまな問題に取り組んでいます。

3)アプリはいくつかの異なるWCFサービスに接続します。それらのそれぞれについて、http接続用の接続プールを維持します

HTTPバインディングは、64Mbのような大きなペイロードを処理するようには設計されていません。バインディングを、より適切なものに変更することを検討してください。そのsieのメッセージを使用する場合、64Mb全体が完全に受信されない限り、WCFはメッセージを渡しません。したがって、10の同時接続がある場合、バッファーサイズは640Mbになります。

その他の質問については、コードとWCF構成を使用してSOに別の質問を投稿してください。問題がどこにあるかを見つけやすくなります。バッファが不適切に使用されているためにクリアされない可能性があります。GCとWCFで実行されたテストの量と、レガシープロジェクトで実行されたテストの量を考慮する必要があります。Occamのかみそりに従ってください。

于 2011-08-31T14:35:19.523 に答える