主なものについて簡単に
XmlSerializerなどの.NET Frameworkの一部は、内部の動的コード生成を使用しますXmlSerializerは、一時的なC#ファイルを作成し、結果のファイルを一時的なアセンブリにコンパイルしてから、そのアセンブリをプロセスに読み込みます。 このコード作成も比較的高価であるため、XmlSerializerは一時的なアセンブリをタイプごとに1つキャッシュに配置します。 これは、次にクラスXのXmlSerializerコードを作成するときに、新しいアセンブリは作成されず、キャッシュからのアセンブリが使用されることを意味します。 ただし、すべてがそれほど単純ではありません。
別のコンストラクターを呼び出すと、XmlSerializerは動的に作成されたアセンブリをキャッシュしませんが、XmlSerializerの新しいインスタンスが作成されるたびに新しい一時的なアセンブリを作成します!
管理されていないメモリリークは、一時的なアセンブリとしてアプリケーションで発生します。
問題の特定
まず、私たちのチームが構築したシステムについてお話します。
アプリケーションは、Webサイト、データウェアハウス、ビジネスセンターの3つの部分で構成されています。
システム全体は.net 3.5上に構築されています。
このWebサイトでは、Windows Workflow Foundationで実行されるビジネスサービスでデータ検証を実行できます。 各ワークフローはいくつかのデータを受信する必要があります(このため、永続化サービスと通信します)。
このシステムは最新のテクノロジーに基づいて構築されており、これらの同じテクノロジーをさまざまに組み合わせて使用すると、予期しないことが起こることは驚くことではありません。
たとえば、一般的にワークフローで動作するワークフロー(WCFサービス)を起動するアプリケーションは、負荷がかかった状態で最大2.5ギガバイトのメモリを消費し始めました。
メモリリークの問題を決定しました。必要なデータが手元にないため、少し後で説明します。
問題を解決した後、アプリケーションでのプロセスは最大500 MB、場合によっては最大800 MBかかりました。 これが限界ではなく、2ギガバイトで以前のパフォーマンスが消えていたことを非常によく知っていました。 ただし、しばらくすると、そのようなボリュームでもアプリケーションは著しく遅くなり始めました。 いくつかの観察の後、C#コンパイラcsc.exeが時々起動されることに気づきました。これは、原則として、ワークフローが最初に要求されたときにのみシステムで起動し、次に、既製のアセンブリを取得する必要があります。
もう少し考えた後、プロセス内のアセンブリの数を調べることにしました。 :)
そして、ここで私たちは驚きました-アプリケーションを起動した直後に約100のアセンブリがメインドメインにアップロードされましたが、そのうち数は3000に達し、後に5000に達しました。
外出先で.netアプリケーションにロードされたドメインとアセンブリを表示できるユーティリティを作成したので、これらの100個の初期アセンブリが残っていることがわかりました。 また、一部の匿名ビルドのみが常に追加されます。 残念ながら、他の誰かのプロセスでは、より詳細な情報(アセンブリで宣言されている型)を取得できませんでした。
テスト環境では、そのような「匿名」アセンブリは多数ありましたが、それらはありませんでした。 詳細な情報を取得するために、アプリケーションで必要な情報を提供するコードを実装することを決定しました。これにより、外出先で最も完全なデータを取得できます。
一般に、「匿名」アセンブリは、シリアル化のためにXmlSerializerによって作成されたアセンブリであることが判明しました。 そして、それらはすべて同じです:)
同じクラスを1000回シリアル化すると想像してください。 そして、あなたのアプリケーションはひどく遅く、さらに、あなたのメモリがリークしています...
いいえ、まあ...これは.netです。 そこにGCがあります。 結局のところ、彼は記憶に従事しています。
実際には、問題
次に、詳細に目を向けます。 .netのXmlSerializerは、アセンブリリークを引き起こす可能性があります(アセンブリリークはメモリリークに流れ込みます)。 もちろん、常にではありません。 このクラスにはいくつかのコンストラクタがあります。
Typeを受け入れる通常のコンストラクタを使用する場合、メモリリークはありません。
名前空間 XmlSerializerMemoryLeak
{
クラスプログラム
{
private static XmlSerializer serial = null ;
static void Main( string [] args)
{
for ( int index = 0; index <10000; index ++)
{
TestClass test = new TestClass();
test.Id =インデックス;
test.Date = DateTime .Now;
StringBuilder builder = new StringBuilder ();
StringWriter writer = new StringWriter (ビルダー);
serial = new XmlSerializer( typeof (TestClass));
serial.Serialize(ライター、テスト);
string xml = builder.ToString();
}
Console .WriteLine( "Done" );
}
}
パブリック クラス TestClass
{
public DateTime Date { get ; セット ; }
public int Id { get ; セット ; }
}
}
*このソースコードは、 ソースコードハイライターで強調表示されました。
ただし、わずかに異なるコンストラクタを使用すると、メモリリークが保証されます。
名前空間 XmlSerializerMemoryLeak
{
クラスプログラム
{
private static XmlSerializer serial = null ;
static void Main( string [] args)
{
Console .ReadLine();
for ( int index = 0; index <100000; index ++)
{
TestClass test = new TestClass();
test.Id =インデックス;
test.Date = DateTime .Now;
StringBuilder builder = new StringBuilder ();
StringWriter writer = new StringWriter (ビルダー);
serial = new XmlSerializer( typeof (TestClass)、 new XmlRootAttribute( "MemoryLeak" ));
serial.Serialize(ライター、テスト);
string xml = builder.ToString();
}
Console .WriteLine( "Done" );
}
}
パブリック クラス TestClass
{
public DateTime Date { get ; セット ; }
public int Id { get ; セット ; }
}
}
*このソースコードは、 ソースコードハイライターで強調表示されました。
それらの間のすべての違いは、 リフレクターに表示されます-最初のもの(および別のもの-XmlSerializer(Type、String))は正常に動作します。 彼はシリアライザーのキャッシュをクロールし、既に何か準備ができているかどうかを確認します。 いいえ-コンパイルしてキャッシュに追加します。
しかし、2つ目は完全にうんざりです。 彼はキャッシュを必要としません。 そのため、毎回新しいアセンブリをコンパイルし、アセンブリリークを呼び出します。
解決策
いくつかの出力があります。
- 「正しい」コンストラクタを使用する
- XmlSerializerCacheを実装します-これは常にキャッシュを検索します。 原則として、実装することはできませんが、 こちらをご覧ください
- シリアル化は使用しないでください。ただし、たとえば、シリアル化を実行できる(または実行することさえできる)アプリケーションが既にある場合は、オブジェクトを渡し、xmlのみを取得できます。
どちらを使用するかは、完全にあなたと状況に依存します。 特定の一般的なプロジェクト、いわばユーティリティクラスがある場合は、アセンブリキャッシュを実装し、将来問題が発生しないように必要なコンストラクターを冷静に使用することをお勧めします。 おそらく、1〜2年で、適切なコンストラクターを忘れることになるでしょう。
結論
これはニュースではありません。この問題はMSDN Magazineで説明されていますが、まだ修正されていない理由は明らかではありません。
結論は簡単です。 シリアル化は非常に慎重に使用し、アプリケーションの状態を注意深く監視してください。 また、アプリケーションに関する最も信頼できる情報を受け取るために、いくつかの診断方法またはサービスを用意しておくと便利です。
PS:
msdnフォーラムで、彼らはこの問題について知っていると答え、私が示したリンクであるMSDN Magazineの記事で説明されていると答えました。 もっと調べてみます。
個人ブログからのクロスポスト