web-dev-qa-db-ja.com

C#のメモリリーク

すべてのハンドル、IDisposeを実装するものが破棄されていることを確認すると、管理対象システムでメモリリークが発生する可能性はありますか?

一部の変数が省略されている場合がありますか?

54
Joan Venge

イベントハンドラは、非自明なメモリリークの非常に一般的な原因です。 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も収集されますが、それまでは収集されません。

63
Reed Copsey

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;
    }
}

これは明らかに愚かな例ですが、マネージランタイムメモリリークに相当します。

40
Michael Meadows

他の人が指摘したように、メモリマネージャに実際のバグがない限り、アンマネージリソースを使用しないクラスはメモリをリークしません。

.NETに表示されるのは、メモリリークではなく、破棄されることのないオブジェクトです。オブジェクトは、ガベージコレクターがオブジェクトグラフで見つけられる限り破棄されません。したがって、生きているオブジェクトにオブジェクトへの参照がある場合、そのオブジェクトは破棄されません。

これを実現するには、イベント登録が良い方法です。オブジェクトがイベントに登録されている場合、登録されたものはすべてそのオブジェクトへの参照を持ち、オブジェクトへの他のすべての参照を削除しても、登録が解除されるまで(または登録したオブジェクトが到達不能になるまで)生き続けます。

そのため、知らないうちに静的イベントに登録するオブジェクトに注意する必要があります。たとえば、ToolStripの気の利いた機能は、表示テーマを変更すると、新しいテーマで自動的に再表示されることです。静的SystemEvents.UserPreferenceChangedイベントに登録することにより、この気の利いた機能を実現します。 Windowsテーマを変更すると、イベントが発生し、イベントをリッスンしているToolStripオブジェクトのすべてに、新しいテーマがあることが通知されます。

さて、フォームでToolStripを捨てることにしたとしましょう:

private void DiscardMyToolstrip()
{
    Controls.Remove("MyToolStrip");
}

これで、死ぬことのないToolStripができました。フォーム上にもう存在していなくても、ユーザーがテーマを変更するたびに、それ以外の場合は存在しないToolStripにWindowsが忠実に伝えます。ガベージコレクターが実行されるたびに、「UserPreferenceChangedイベントがそのオブジェクトを使用しているため、そのオブジェクトを捨てることはできません」と考えられます。

これはメモリリークではありません。しかし、そうかもしれません。

このようなことは、メモリプロファイラを非常に貴重なものにします。メモリプロファイラを実行すると、「奇妙なことに、フォーム上に1つしか存在しないにもかかわらず、ヒープ上に10,000個のToolStripオブジェクトがあるように見えます。これはどのように発生しましたか?」

ああ、なぜプロパティセッターが悪だと思うのか疑問に思う場合は、ToolStripUserPreferenceChangedイベントから登録解除するには、そのVisibleプロパティをfalseに設定してください。

27
Robert Rossney

デリゲートは、直感的でないメモリリークを引き起こす可能性があります。

インスタンスメソッドからデリゲートを作成するたびに、そのインスタンスへの参照がそのデリゲートに「格納」されます。

また、複数のデリゲートをマルチキャストデリゲートに結合すると、そのマルチキャストデリゲートがどこかで使用されている限り、ガベージコレクションから保護される多数のオブジェクトへの参照の1つの大きな塊があります。

20

WinFormsアプリケーションを開発している場合、微妙な「リーク」はControl.AllowDropプロパティ(ドラッグアンドドロップを有効にするために使用)。 AllowDropが「true」に設定されている場合、CLRはSystem.Windows.Forms.DropTarget。これを修正するには、ControlAllowDropプロパティが不要になったときにfalseに設定し、CLRが残りを処理するようにします。

17
Zach Johnson

.NETアプリケーションでメモリリークが発生する唯一の理由は、オブジェクトの寿命が終了したにもかかわらず、オブジェクトがまだ参照されていることです。したがって、ガベージコレクターはそれらを収集できません。そして、それらは長寿命のオブジェクトになります。

オブジェクトの寿命が終了したときにサブスクライブを解除せずにイベントをサブスクライブすることで、リークを引き起こすのは非常に簡単だと思います。

7
tranmq

既に述べたように、参照を保持することは、時間の経過とともにメモリ使用量を増やすことにつながります。この状況に入る簡単な方法は、イベントを使用することです。他のオブジェクトがリッスンするイベントを持つ長期生存オブジェクトがあり、リスナーが削除されない場合、長期生存オブジェクトのイベントは、それらのその他のインスタンスが不要になった後も長時間存続します。

7
toad
6
Fabrice

Reflection emit は、別の潜在的なリークの原因です。組み込みのオブジェクトデシリアライザーと派手なSOAP/XMLクライアント。少なくとも以前のバージョンのフレームワークでは、依存AppDomainsで生成されたコードはアンロードされませんでした...

5
Pontus Gagge

マネージコードでメモリをリークできないというのは神話です。確かに、アンマネージC++よりもはるかに困難ですが、それを行う方法は数百万あります。参照、不要な参照、キャッシングなどを保持する静的オブジェクト。「正しい」方法で作業を行っている場合、オブジェクトの多くは必要以上に遅くなるまでガベージコレクションされません。実用的であり、理論的ではありません。

幸いなことに、あなたを助けることができるツールがあります。私はMicrosoftの CLR Profiler を多く使用しています。これは、これまでに書かれた中で最もユーザーフレンドリーなツールではありませんが、非常に便利であり、無料です。

4
Tamas Czinege

オブジェクトへのすべての参照がなくなると、ガベージコレクターは次のパスでそのオブジェクトを解放します。メモリをリークすることは不可能だとは言いませんが、リークするためには、気付かないうちに座っているオブジェクトへの参照が必要になります。

たとえば、オブジェクトをリストにインスタンス化し、完了したらリストから削除することを忘れ、破棄することを忘れた場合。

2
Kevin Laity

管理されていないリソースが適切にクリーニングされない場合、リークが発生する可能性があります。 IDisposable を実装するクラスはリークする可能性があります。

ただし、通常のオブジェクト参照では、低レベル言語のように明示的なメモリ管理は必要ありません。

1
Ben S

実際にはメモリリークではありませんが、大きなオブジェクト(正しく覚えていれば64K以上)を使用すると、メモリ不足に陥りやすいです。それらはLOHに保存され、デフラグされません。したがって、これらのラージオブジェクトを使用してそれらを解放すると、LOH上のメモリが解放されますが、その空きメモリは、このプロセスの.NETランタイムによって使用されなくなります。そのため、LOHのいくつかの大きなオブジェクトを使用するだけで、LOHのスペースを簡単に使い果たすことができます。この問題はマイクロソフトに知られていますが、私が今覚えているように、これに対する解決策が計画されています。

1

自己リマインダーメモリリークを見つける方法:

  • 呼び出しを削除し、gc.collectを呼び出します。
  • メモリがリークしていることを確認するまで待ちます。
  • タスクマネージャからダンプファイルを作成します。
  • DebugDiag を使用してダンプファイルを開きます。
  • 最初に結果を分析します。そこからの結果は私たちを助けるはずです。これは通常、ほとんどのメモリを消費します。
  • メモリリークがなくなるまでコードを修正します。
  • .netプロファイラーなどのサードパーティアプリケーションを使用します。 (試用版を使用できますが、できるだけ早く問題を解決する必要があります。最初のダンプは、主に漏れの方法について役立つはずです)
  • 問題が仮想メモリにある場合は、管理されていないメモリを監視する必要があります。 (通常、有効にする必要がある別の構成があります)
  • 使用方法に基づいてサードパーティのアプリケーションを実行します。

一般的なメモリリークの問題:

  • イベント/デリゲートは削除されません。 (破棄するときは、イベントが未登録であることを確認してください)- ReepChopsey answerを参照してください
  • リスト/辞書はクリアされませんでした。
  • メモリに保存されている別のオブジェクトを参照するオブジェクトは破棄されません。 (管理しやすくするために複製してください)
0
Kosmas

メモリリークと見なされる場合は、次の種類のコードでも実現できます。

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();
    }
}
0
meJustAndrew

小さな関数は、「メモリリーク」の回避に役立ちます。ガベージコレクターは関数の最後でローカル変数を解放するためです。関数が大きく、多くのメモリを必要とする場合、多くのメモリを必要とし、もはや必要ではないローカル変数を自分で解放する必要があります。同様に、グローバル変数(配列、リスト)も悪いです。

画像を作成してそれらを破棄しないときに、C#でメモリリークが発生しました。これは少し奇妙です。人々は、それを持つすべてのオブジェクトで.Dispose()を呼び出さなければならないと言います。ただし、グラフィカルなC#関数のドキュメントでは、たとえばGetThumbnailImage()のように、常にこれに言及しているわけではありません。 C#コンパイラはこれについて警告するはずです。

0
Tone Škoda

.NET XmlSerializerを使用するときにメモリリークが発生する可能性があります。これは、下でアンマネージコードが使用され、そのコードが破棄されないためです。

ドキュメントを参照し、このページで「メモリリーク」を検索してください。

https://docs.Microsoft.com/en-us/dotnet/api/system.xml.serialization.xmlserializer?view=netframework-4.7.2

0
FrankyHollywood

私の最後の仕事では、ふるいのように漏れるサードパーティの.NET SQLiteライブラリを使用していました。

毎回データベース接続を開いたり閉じたりしなければならないという奇妙な状況で、多くの高速データ挿入を行っていました。サードパーティのライブラリは、手動で行うことになっていたものと同じ接続の一部を実行し、それを文書化しませんでした。また、私たちが見つけられなかったどこかに参照を保持しました。結果は、想定されていた数の2倍の接続が開かれ、閉じられた接続はわずか1/2でした。また、参照が保持されたため、メモリリークが発生しました。

これは明らかに、従来のC/C++メモリリークと同じではありませんが、すべての意図と目的のために1つでした。

0
Dinah

フレームワーク内の何かにリークがある可能性はありますが、おそらく適切に廃棄されていないものがあるか、何かがGCによる廃棄をブロックしている可能性があります、IISこれの最有力候補になります。

.NETのすべてが完全に管理されたコード、COM相互運用、ファイルストリームのようなファイルio、DBリクエスト、イメージなどではないことを覚えておいてください。

私たちがしばらく前に抱えていた問題(IIS 6)の.net 2.0)は、画像を作成してから破棄するということでしたが、IISはしばらくメモリを解放します。

0
Bob The Janitor

唯一のリーク(ランタイムに存在する可能性のあるバグを除くが、ガベージコレクションが原因ではない可能性が高い)は、ネイティブリソースに対するものです。ファイルハンドル、ソケット接続、または管理対象アプリケーションに代わって何かを開くネイティブライブラリにP/Invokeし、それらを明示的に閉じない(およびディスポーザまたはデストラクタ/ファイナライザで処理しない)場合、次のことができます。ランタイムがこれらすべてを自動的に管理できないため、メモリまたはリソースのリークが発生します。

ただし、純粋に管理されたリソースに固執する場合は、問題ないはずです。ネイティブコードを呼び出さずにメモリリークが発生した場合、それはバグです。

0
Michael Trausch

コンソールまたは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();

Controls.Clear()の呼び出しでのメモリリーク

Clearメソッドを呼び出しても、メモリからコントロールハンドルは削除されません。メモリリークを回避するには、Disposeメソッドを明示的に呼び出す必要があります

0
Daniel B