すべてのハンドル、IDispose
を実装するものが破棄されていることを確認すると、管理対象システムでメモリリークが発生する可能性はありますか?
一部の変数が省略されている場合がありますか?
イベントハンドラは、非自明なメモリリークの非常に一般的な原因です。 object2からobject1のイベントにサブスクライブし、object2.Dispose()を実行して、存在しないふりをする(そして、コードからすべての参照をドロップアウトする)場合、object1のイベントには、object2を防ぐ暗黙の参照がありますゴミ収集。
MyType object2 = new MyType();
// ...
object1.SomeEvent += object2.myEventHandler;
// ...
// Should call this
// object1.SomeEvent -= object2.myEventHandler;
object2.Dispose();
これは、リークの一般的なケースです。イベントの登録を簡単に解除するのを忘れます。もちろん、object1が収集されると、object2も収集されますが、それまでは収集されません。
C++スタイルのメモリリークは考えられません。ガベージコレクターはそれらを説明する必要があります。オブジェクトが再び使用されることはありませんが、オブジェクト参照を集約する静的オブジェクトを作成することは可能です。このようなもの:
public static class SomethingFactory
{
private static List<Something> listOfSomethings = new List<Something>();
public static Something CreateSomething()
{
var something = new Something();
listOfSomethings.Add(something);
return something;
}
}
これは明らかに愚かな例ですが、マネージランタイムメモリリークに相当します。
他の人が指摘したように、メモリマネージャに実際のバグがない限り、アンマネージリソースを使用しないクラスはメモリをリークしません。
.NETに表示されるのは、メモリリークではなく、破棄されることのないオブジェクトです。オブジェクトは、ガベージコレクターがオブジェクトグラフで見つけられる限り破棄されません。したがって、生きているオブジェクトにオブジェクトへの参照がある場合、そのオブジェクトは破棄されません。
これを実現するには、イベント登録が良い方法です。オブジェクトがイベントに登録されている場合、登録されたものはすべてそのオブジェクトへの参照を持ち、オブジェクトへの他のすべての参照を削除しても、登録が解除されるまで(または登録したオブジェクトが到達不能になるまで)生き続けます。
そのため、知らないうちに静的イベントに登録するオブジェクトに注意する必要があります。たとえば、ToolStrip
の気の利いた機能は、表示テーマを変更すると、新しいテーマで自動的に再表示されることです。静的SystemEvents.UserPreferenceChanged
イベントに登録することにより、この気の利いた機能を実現します。 Windowsテーマを変更すると、イベントが発生し、イベントをリッスンしているToolStrip
オブジェクトのすべてに、新しいテーマがあることが通知されます。
さて、フォームでToolStrip
を捨てることにしたとしましょう:
private void DiscardMyToolstrip()
{
Controls.Remove("MyToolStrip");
}
これで、死ぬことのないToolStrip
ができました。フォーム上にもう存在していなくても、ユーザーがテーマを変更するたびに、それ以外の場合は存在しないToolStrip
にWindowsが忠実に伝えます。ガベージコレクターが実行されるたびに、「UserPreferenceChanged
イベントがそのオブジェクトを使用しているため、そのオブジェクトを捨てることはできません」と考えられます。
これはメモリリークではありません。しかし、そうかもしれません。
このようなことは、メモリプロファイラを非常に貴重なものにします。メモリプロファイラを実行すると、「奇妙なことに、フォーム上に1つしか存在しないにもかかわらず、ヒープ上に10,000個のToolStrip
オブジェクトがあるように見えます。これはどのように発生しましたか?」
ああ、なぜプロパティセッターが悪だと思うのか疑問に思う場合は、ToolStrip
をUserPreferenceChanged
イベントから登録解除するには、そのVisible
プロパティをfalse
に設定してください。
デリゲートは、直感的でないメモリリークを引き起こす可能性があります。
インスタンスメソッドからデリゲートを作成するたびに、そのインスタンスへの参照がそのデリゲートに「格納」されます。
また、複数のデリゲートをマルチキャストデリゲートに結合すると、そのマルチキャストデリゲートがどこかで使用されている限り、ガベージコレクションから保護される多数のオブジェクトへの参照の1つの大きな塊があります。
WinFormsアプリケーションを開発している場合、微妙な「リーク」はControl.AllowDrop
プロパティ(ドラッグアンドドロップを有効にするために使用)。 AllowDrop
が「true」に設定されている場合、CLRはSystem.Windows.Forms.DropTarget
。これを修正するには、Control
のAllowDrop
プロパティが不要になったときにfalse
に設定し、CLRが残りを処理するようにします。
.NETアプリケーションでメモリリークが発生する唯一の理由は、オブジェクトの寿命が終了したにもかかわらず、オブジェクトがまだ参照されていることです。したがって、ガベージコレクターはそれらを収集できません。そして、それらは長寿命のオブジェクトになります。
オブジェクトの寿命が終了したときにサブスクライブを解除せずにイベントをサブスクライブすることで、リークを引き起こすのは非常に簡単だと思います。
既に述べたように、参照を保持することは、時間の経過とともにメモリ使用量を増やすことにつながります。この状況に入る簡単な方法は、イベントを使用することです。他のオブジェクトがリッスンするイベントを持つ長期生存オブジェクトがあり、リスナーが削除されない場合、長期生存オブジェクトのイベントは、それらのその他のインスタンスが不要になった後も長時間存続します。
私の新しい記事が役に立つかもしれません: 。NETアプリケーションでメモリとリソースのリークを検出して回避する方法
Reflection emit は、別の潜在的なリークの原因です。組み込みのオブジェクトデシリアライザーと派手なSOAP/XMLクライアント。少なくとも以前のバージョンのフレームワークでは、依存AppDomainsで生成されたコードはアンロードされませんでした...
マネージコードでメモリをリークできないというのは神話です。確かに、アンマネージC++よりもはるかに困難ですが、それを行う方法は数百万あります。参照、不要な参照、キャッシングなどを保持する静的オブジェクト。「正しい」方法で作業を行っている場合、オブジェクトの多くは必要以上に遅くなるまでガベージコレクションされません。実用的であり、理論的ではありません。
幸いなことに、あなたを助けることができるツールがあります。私はMicrosoftの CLR Profiler を多く使用しています。これは、これまでに書かれた中で最もユーザーフレンドリーなツールではありませんが、非常に便利であり、無料です。
オブジェクトへのすべての参照がなくなると、ガベージコレクターは次のパスでそのオブジェクトを解放します。メモリをリークすることは不可能だとは言いませんが、リークするためには、気付かないうちに座っているオブジェクトへの参照が必要になります。
たとえば、オブジェクトをリストにインスタンス化し、完了したらリストから削除することを忘れ、破棄することを忘れた場合。
管理されていないリソースが適切にクリーニングされない場合、リークが発生する可能性があります。 IDisposable を実装するクラスはリークする可能性があります。
ただし、通常のオブジェクト参照では、低レベル言語のように明示的なメモリ管理は必要ありません。
実際にはメモリリークではありませんが、大きなオブジェクト(正しく覚えていれば64K以上)を使用すると、メモリ不足に陥りやすいです。それらはLOHに保存され、デフラグされません。したがって、これらのラージオブジェクトを使用してそれらを解放すると、LOH上のメモリが解放されますが、その空きメモリは、このプロセスの.NETランタイムによって使用されなくなります。そのため、LOHのいくつかの大きなオブジェクトを使用するだけで、LOHのスペースを簡単に使い果たすことができます。この問題はマイクロソフトに知られていますが、私が今覚えているように、これに対する解決策が計画されています。
自己リマインダーメモリリークを見つける方法:
一般的なメモリリークの問題:
メモリリークと見なされる場合は、次の種類のコードでも実現できます。
public class A
{
B b;
public A(B b) { this.b = b; }
~A()
{
b = new B();
}
}
public class B
{
A a;
public B() { this.a = new A(this); }
~B()
{
a = new A(this);
}
}
class Program
{
static void Main(string[] args)
{
{
B[] toBeLost = new B[100000000];
foreach (var c in toBeLost)
{
toBeLost.ToString(); //to make JIT compiler run the instantiation above
}
}
Console.ReadLine();
}
}
小さな関数は、「メモリリーク」の回避に役立ちます。ガベージコレクターは関数の最後でローカル変数を解放するためです。関数が大きく、多くのメモリを必要とする場合、多くのメモリを必要とし、もはや必要ではないローカル変数を自分で解放する必要があります。同様に、グローバル変数(配列、リスト)も悪いです。
画像を作成してそれらを破棄しないときに、C#でメモリリークが発生しました。これは少し奇妙です。人々は、それを持つすべてのオブジェクトで.Dispose()を呼び出さなければならないと言います。ただし、グラフィカルなC#関数のドキュメントでは、たとえばGetThumbnailImage()のように、常にこれに言及しているわけではありません。 C#コンパイラはこれについて警告するはずです。
.NET XmlSerializerを使用するときにメモリリークが発生する可能性があります。これは、下でアンマネージコードが使用され、そのコードが破棄されないためです。
ドキュメントを参照し、このページで「メモリリーク」を検索してください。
私の最後の仕事では、ふるいのように漏れるサードパーティの.NET SQLiteライブラリを使用していました。
毎回データベース接続を開いたり閉じたりしなければならないという奇妙な状況で、多くの高速データ挿入を行っていました。サードパーティのライブラリは、手動で行うことになっていたものと同じ接続の一部を実行し、それを文書化しませんでした。また、私たちが見つけられなかったどこかに参照を保持しました。結果は、想定されていた数の2倍の接続が開かれ、閉じられた接続はわずか1/2でした。また、参照が保持されたため、メモリリークが発生しました。
これは明らかに、従来のC/C++メモリリークと同じではありませんが、すべての意図と目的のために1つでした。
フレームワーク内の何かにリークがある可能性はありますが、おそらく適切に廃棄されていないものがあるか、何かがGCによる廃棄をブロックしている可能性があります、IISこれの最有力候補になります。
.NETのすべてが完全に管理されたコード、COM相互運用、ファイルストリームのようなファイルio、DBリクエスト、イメージなどではないことを覚えておいてください。
私たちがしばらく前に抱えていた問題(IIS 6)の.net 2.0)は、画像を作成してから破棄するということでしたが、IISはしばらくメモリを解放します。
唯一のリーク(ランタイムに存在する可能性のあるバグを除くが、ガベージコレクションが原因ではない可能性が高い)は、ネイティブリソースに対するものです。ファイルハンドル、ソケット接続、または管理対象アプリケーションに代わって何かを開くネイティブライブラリにP/Invokeし、それらを明示的に閉じない(およびディスポーザまたはデストラクタ/ファイナライザで処理しない)場合、次のことができます。ランタイムがこれらすべてを自動的に管理できないため、メモリまたはリソースのリークが発生します。
ただし、純粋に管理されたリソースに固執する場合は、問題ないはずです。ネイティブコードを呼び出さずにメモリリークが発生した場合、それはバグです。
コンソールまたはWindowsアプリでPanel
オブジェクト(panel1)を作成し、PictureBox
プロパティが設定された1000 Image
を追加してからpanel1.Controls.Clear
。すべてのPictureBoxコントロールはまだメモリ内にあり、GC
がそれらを収集することはできません。
var panel1 = new Panel();
var image = Image.FromFile("image/heavy.png");
for(var i = 0; i < 1000;++i){
panel1.Controls.Add(new PictureBox(){Image = image});
}
panel1.Controls.Clear(); // => Memory Leak!
それを行う正しい方法は
for (int i = panel1.Controls.Count-1; i >= 0; --i)
panel1.Controls[i].Dispose();
Clearメソッドを呼び出しても、メモリからコントロールハンドルは削除されません。メモリリークを回避するには、Disposeメソッドを明示的に呼び出す必要があります