10

次のコードは、私が見ている問題の簡単な例です。このアプリケーションは、ディクショナリが大きすぎるため、例外をスローする前に約 4 GB のメモリを消費します。

 class Program
 {
    static void Main(string[] args)
    {
        Program program = new Program();

        while(true)
        {
            program.Method();
            Console.ReadLine();
        }
    }

    public void Method()
    {
        WasteOfMemory memory = new WasteOfMemory();
        Task tast = new Task(memory.WasteMemory);
        tast.Start();
    }


}

public class WasteOfMemory
{
     public void WasteMemory()
     {
         Dictionary<string, string> aMassiveList = new Dictionary<string, string>();

         try
         {
             long i = 0;
             while (true)
             {
                 aMassiveList.Add(i.ToString(), "I am a line of text designed to waste space.... I am exceptionally useful........");
                 i++;
             }

         }
         catch(Exception e)
         {
             Console.WriteLine("I have broken myself");
         }
     }
}

これはすべて予想どおりですが、現時点で解決できないのは、このメモリを CLR からいつ解放する必要があるかということです。

タスクを完了させ、メモリ過負荷の状況をシミュレートしましたが、ディクショナリによって消費されたメモリは解放されません。OS のメモリが不足しているため、CLR にメモリを解放する圧力がかかっていませんか?

しかし、さらにややこしいのは、タスクが完了するまで待ってから Enter キーを押してタスクを再度実行すると、メモリが解放されるため、明らかに前の辞書がガベージ コレクションされていることです (そうではありませんか?)。

では、なぜメモリが解放されないのでしょうか。CLR にメモリを解放させるにはどうすればよいでしょうか。

説明や解決策をいただければ幸いです。

編集: 次の返信、特に Beska の返信、問題の私の説明が最も明確ではないことは明らかなので、明確にしようと思います。

コードは最良の例ではないかもしれません。申し訳ありません! 問題を再現しようとするのは、簡単で大雑把なコードでした。

ここでディクショナリを使用して、大規模なカスタム データ オブジェクトがあり、メモリの大部分を占有し、タスクの完了後に解放されないという事実を再現します。

この例では、ディクショナリはディクショナリの制限までいっぱいになり、例外をスローします。永久にいっぱいになるわけではありません! これはメモリがいっぱいになるかなり前のことであり、OutOfMemoryException は発生しません。したがって、結果はメモリ内の大きなオブジェクトになり、タスクは完了します。

この時点で、タスクとメソッド「メソッド」の両方が完了しているため、ディクショナリは範囲外であることが予想されます。したがって、ディクショナリがガベージ コレクションされ、メモリが再利用されることが期待されます。実際には、「メソッド」が再度呼び出されて新しい WasteOfMemory インスタンスが作成され、新しいタスクが開始されるまで、メモリは解放されません。

うまくいけば、それは問題を少し明確にするでしょう

4

6 に答える 6

6

ガベージ コレクターは、使用されなくなったメモリ内の場所 (それらを指しているポインターを持たないオブジェクト) のみを解放します。

(1)プログラムは終了せずに無限に実行され、

(2)辞書へのポインターを決して変更しないため、GC が辞書に触れる理由はまったくありません。

だから私にとって、あなたのプログラムはまさにそれがやるべきことをやっています。

于 2012-07-03T17:15:33.590 に答える
2

わかりました、私はこれをフォローしてきました...いくつかの問題があり、そのうちのいくつかは人々が触れていると思いますが、本当の質問に答えていないと思います(確かに、認識するのに時間がかかりました.今でもあなたが望むものに答えているかどうかわかりません。)

これはすべて予想どおりですが、現時点で解決できないのは、このメモリを CLR からいつ解放する必要があるかということです。

他の人が言ったように、タスクの実行中、辞書は解放されません。使用されています。メモリがなくなるまで大きくなります。私はあなたがこれを理解していると確信しています。

タスクを完了させ、メモリ過負荷の状況をシミュレートしましたが、ディクショナリによって消費されたメモリは解放されません。OS のメモリが不足しているため、CLR にメモリを解放する圧力がかかっていませんか?

ここが本当の問題だと思います。

私の理解が正しければ、メモリをいっぱいにするためにこれを設定したと言っているのですね。そして、クラッシュした後 (ただし、Return キーを押して新しいタスクを開始する前) 、Windows で他のプログラムを実行して GC にメモリを収集させるなど、このプログラムの外部で他のことを試みていますよね? OS が GC と対話し、それを実行するように圧力をかけ始めることを期待しています。

しかし、さらにややこしいのは、タスクが完了するまで待ってから Enter キーを押してタスクを再度実行すると、メモリが解放されるため、明らかに前の辞書がガベージ コレクションされていることです (そうではありませんか?)。

あなたはあなた自身の質問に答えたと思います...新しいタスクを開始するためにリターンを押すまで、必ずしもリリースされていません. 新しいタスクはメモリを必要とするため、GC に送られ、GC は (フル メモリからスローした後に) 終了した前のタスクからメモリを喜んで収集します。

では、なぜメモリが解放されないのでしょうか。CLR にメモリを解放させるにはどうすればよいでしょうか。

GCに強制的にメモリを解放させることができるかどうかはわかりません。一般的に言えば、必要なときにそれを実行します (一部のハッカー タイプは、巧妙な方法で強制的に実行することを知っている場合があります)。もちろん、.NET は GC を実行するタイミングを決定します。必要がないと判断しているのかもしれません。OS が GC を実行するように圧力をかけることができるかどうかについては、テストからの答えは「いいえ」のようです。少し直感に反するかもしれません。

それはあなたが得ようとしていたものですか?

于 2012-07-03T18:38:21.770 に答える
2

スコープaMassiveListが終了していないため、メモリは解放されていません。関数が戻ると、関数内で作成された参照されていないすべてのリソースが解放されます。

あなたの場合、aMassiveList決して文脈を離れることはありません。関数が二度と戻らないようにしたい場合は、すべての情報を永久に保存するのではなく、情報を「処理」して解放する方法を見つける必要があります。

ますますリソースを割り当て、決して解放しない関数を作成すると、すべてのメモリを消費することになります。

于 2012-07-03T17:15:10.570 に答える
2

GC は参照されていないオブジェクトのみを解放するため、ディクショナリはプログラムによって参照されているため、GC によって解放することはできません。

于 2012-07-03T17:18:15.957 に答える
0

WasteMemory メソッドを記述した方法では、(今年は発生しない変数 "i" のオーバーフローがない限り) 終了することはありませ

Daniel White の言うとおりです。GC のしくみについて読む必要があります。

参照が使用中の場合、GC は参照されたメモリを収集しません。そうでなければ、プログラムはどのように機能しますか?

ここで CLR/GC に何を期待しているのかわかりません。WasteMemory メソッドの 1 回の実行でガベージ コレクションを行う必要はありません。

しかし、さらにややこしいのは、タスクが完了するまで待ってから、Enter キーを押してタスクを再度実行すると、メモリが解放されるため、明らかに前の辞書がガベージ コレクションされていることです (そうではありませんか?)。

Enter キーを押すと、新しいタスクが作成されて開始されます。これは同じタスクではなく、新しいタスクです。つまり、新しい WasteOfMemory インスタンスへの参照を保持する新しいオブジェクトです。

古いタスクはバックグラウンドで実行され続け、そのメモリを使用し続けるため、古いタスクは実行され続け、それが使用するメモリは収集されません

古いタスクのメモリが解放されているのを観察する理由、そして最も重要な方法がわかりません。

于 2012-07-03T17:49:39.880 に答える
-8

メソッドを using ステートメントに変更します

例:

Using (WateOfMemory memory = new WateOfMemory())
{
    Task tast = new Task(memory.WasteMemory); 
    tast.Start();
 }

そして、使い捨て可能なWateOfMemoryClassを追加します(コンストラクターはWasteOfMemoryです)

#region Dispose
    private IntPtr handle;
    private Component component = new Component();
    private bool disposed = false;

    public WateOfMemory()
    {

    }

    public WateOfMemory(IntPtr handle)
    {
        this.handle = handle;
    }

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    private void Dispose(bool disposing)
    {
        if(!this.disposed)
        {
            if(disposing)
            {
            component.Dispose();
            }

            CloseHandle(handle);
            handle = IntPtr.Zero;            
        }
        disposed = true;         
    }

    [System.Runtime.InteropServices.DllImport("Kernel32")]
    private extern static Boolean CloseHandle(IntPtr handle);

    ~WateOfMemory()      
    {
        Dispose(false);
    }
    #endregion
于 2012-07-03T17:16:49.313 に答える