【C#学習ノート】メモリ管理

ここに画像の説明を挿入します


公式ドキュメント

自動メモリ管理

自動メモリ管理は、管理実行中に CLR によって提供されるサービスの 1 つです。共通言語ランタイムのガベージ コレクターは、アプリケーションのメモリの割り当てと割り当て解除を管理します。開発者にとって、これは、メモリ管理タスクを実行するコードを作成することなくマネージド アプリケーションを開発できることを意味します。自動メモリ管理は、オブジェクトの解放を忘れてメモリ リークが発生したり、解放されたオブジェクトのメモリにアクセスしようとしたりするなどの一般的な問題を解決します。

メモリの割り当て

一般に、C# メモリは 4 つのブロックに分割されます。

  • グローバルデータ領域: グローバル変数、静的データ、定数を格納します。
  • コード領域: すべてのプログラムコードを格納します
  • スタック領域:演算用に割り当てられたローカル変数、パラメータ、戻りデータ、戻りアドレスなどを格納します。
  • ヒープ領域:空き記憶領域

プロセス内でコードが実行される 2 つの主な領域は、スタックとヒープです。スタックでは、コードは実行のために順番にスタックにプッシュインおよびスタックからプッシュされます。各スレッドは独自のスレッド スタックを維持し、スタック領域のメモリ アドレスは上から下に増加します: 記事「Understanding C#'s Heap, Stack ,」
より抜粋値型と参照型は
C#のヒープとスタックを深く理解して一気にマスターしましょう!
C#のガベージコレクションの仕組みを詳しく解説
ここに画像の説明を挿入します

多くのコンピューター専門家は、スタック内のコードの実行については十分に理解していると思います。ヒープの場合はそうではなく、新しいプロセスが初期化されると、ランタイムはプロセス用に連続したアドレス空間領域を予約します。この予約されたアドレス空間はマネージド ヒープと呼ばれます。

マネージド ヒープは C# の自動メモリ管理専用であり、CLR によって管理されます。

マネージド ヒープは、ヒープ内に割り当てられる次のオブジェクトのアドレスへのポインタを保持します。最初に、このポインタはマネージド ヒープのベース アドレスを指すように設定されます。スタックとは異なり、ヒープは下から上に構築されるため、すべての空き領域は使用済み領域の上にあります。スタックと異なる最大の特徴は、ヒープにアクセスする際に、任意の順序で自由にアクセスおよび削除できることです。
ここに画像の説明を挿入します
(乱雑な山)

C# には 2 種類の変数があると前に述べたことを思い出します。1 つ目は値型で、スタックに直接保存されます。値型を使用して値を割り当てる場合は、完全なコピーを作成して保存する必要があります。それをスタック上に置きます。スタックでの操作は高速で、メモリの割り当てと解放が簡単であるため、操作はすべてスタック上で実装されます。

参照型の方が便利である一方で、参照型のオントロジーはヒープに保存され、ヒープへのアクセスがより自由になります。一方、ヒープ アドレス変数はスタックに保存します。参照型にアクセスしたいときは、スタック上のアドレスで参照型を見つけるだけです。ただし、データ ストレージがランダムすぎるため、ヒープに直接アクセスすることはできず、スタック内のポインタがなければ、それがヒープ内のどこにあるのかわかりません。

ここに画像の説明を挿入します
しかし、ヒープを使用すると問題が発生します。スタック上では、プログラムが実行されると、スタックにプッシュされたさまざまな変数が自動的に解放されます。ただし、ヒープは最初は任意にアクセスできるため、プログラマが使用した変数を解放し忘れるとメモリが無駄に消費されます。第二に、ヒープ内の変数が解放されても、解放された変数に対応するメモリは空になります。連続した空きメモリが少なすぎてまったく使用できない場合、メモリの断片化が発生します。したがって、C# はマネージド ヒープを使用し、GC メカニズムはこのヒープを自動的に管理するのに役立ちます。

アプリケーションが最初の参照型を作成すると、マネージド ヒープのベース アドレスにある型にメモリが割り当てられます。
アプリケーションが次のオブジェクトを作成すると、ガベージ コレクターは最初のオブジェクトの直後のアドレス空間にそのオブジェクト用のメモリを割り当てます。
ガベージ コレクターは、アドレス スペースが利用可能な限り、この方法で新しいオブジェクトにスペースを割り当て続けます。

マネージド ヒープからのメモリの割り当ては、アンマネージド メモリの割り当てよりも高速です。ランタイムはポインターに値を追加することによってオブジェクトにメモリを割り当てるため、これはスタックからメモリを割り当てるのとほぼ同じ速度になります。
さらに、継続的に割り当てられる新しいオブジェクトはマネージド ヒープに連続して格納されるため、アプリケーションはこれらのオブジェクトに迅速にアクセスできます。

概要: スタックは主にローカル変数とメソッド呼び出し情報の保存に使用され、その動作速度は速くなります。一方、ヒープは主に動的に割り当てられたオブジェクトの保存に使用され、動作速度は遅くなりますが、より長いライフサイクルとオブジェクト共有をサポートできます。 。データが小さくライフサイクルが短い変数の場合はスタックの使用を検討できますが、より大きなオブジェクトや長期間存続する必要があるオブジェクトの場合はヒープの使用が必要です。


空きメモリ

GC メカニズムがどのようにメモリを解放するかを理解しましょう。

.NET のガベージ コレクターは、アプリケーションのメモリの割り当てと割り当て解除を管理します。オブジェクトが作成されるたびに、共通言語ランタイムはマネージド ヒープからオブジェクトにメモリを割り当てます。マネージド ヒープにアドレス スペースがある限り、ランタイムは新しいオブジェクトにスペースを割り当て続けます。ただし、メモリは無制限ではありません。ガベージ コレクターは、最終的にガベージ コレクションを実行してメモリの一部を解放する必要があります。ガベージ コレクターの最適化エンジンは、実行された割り当てに基づいてコレクションを実行する最適な時間を決定します。コレクションが実行されると、ガベージ コレクターは、アプリケーションによって使用されなくなったオブジェクトのマネージド ヒープをチェックし、メモリを再利用するために必要な操作を実行します。

ガベージ コレクションは、次の条件のいずれかが満たされた場合に発生します。

  • システムの物理メモリが少なくなっています。メモリ サイズは、オペレーティング システムからのメモリ不足通知、またはホストからのメモリ不足通知を通じて検出されます。

  • マネージド ヒープ上で割り当てられたオブジェクトによって使用されるメモリが、許容可能なしきい値を超えています。このしきい値は、プロセスの実行中に継続的に調整されます。

  • GC.Collect メソッドを呼び出します。ほとんどの場合、ガベージ コレクターは引き続き実行されるため、このメソッドを呼び出す必要はありません。このメソッドは主に特殊なケースやテストに使用されます。

GC

全体的に見て、C# の GC と Lua の GC は実際には似ています。

参照追跡: GC の最初のステップは、参照追跡を通じてアクティブな参照によってどのオブジェクトがまだ参照されているかを判断することです。GC は、メソッド内のローカル変数と静的変数を含むスタック上のルート オブジェクトから開始されます。次に、ルート オブジェクトから開始して参照チェーンをトレースし、到達可能なすべてのオブジェクトを見つけます。

ルート オブジェクトのマーキング: 参照追跡プロセス中に、GC は到達可能なすべてのオブジェクトをライブ オブジェクトとしてマークします。これらのライブ オブジェクトは保持され、クリーンアップされません。

マークアンドスイープアルゴリズム

オブジェクトのマーキング: GC は次に、ルート オブジェクトから到達可能なオブジェクトから始めてヒープ内のすべてのオブジェクトを走査し、それらをライブ オブジェクトとしてマークします。GC は、「マーク アンド スイープ」と呼ばれるアルゴリズムを使用してオブジェクトをマークします。まず、ヒープ内のすべてのオブジェクトはデフォルトでガベージですが、その後、スタックを走査することで参照オブジェクトが見つかり、これらの参照オブジェクトにマークが付けられます。最後に、すべてのガベージ メモリが解放されます (または空きとしてマークされます)。

圧縮フェーズ: ガベージをクリーンアップした後、ヒープ内のメモリが分散されるため、メモリの断片化を避けるために、生き残ったすべてのオブジェクトがヒープの一番下に移動されます。

ここに画像の説明を挿入します

世代別アルゴリズム

生成アルゴリズムの原理は統計に基づいており、簡単に言うと、オブジェクトのアクティブ時間が長くなるほど、使用率が高くなり、死亡率が低くなり、クリーンアップの必要性が低くなります。

世代アルゴリズムの前提条件:
1. 多数の新しく作成されたオブジェクトのライフ サイクルは短く、古いオブジェクトのライフ サイクルは長くなります 2. メモリの
一部のリサイクルは、メモリ全体に基づいてリサイクルするよりも高速です 3.
新しい作成されたオブジェクトは通常、互いに強く関連しています。ヒープによって割り当てられたオブジェクトは連続的であり、強い相関関係は CPU キャッシュのヒット率の向上に役立ちます。

.NET は、ヒープを Gen 0、Gen 1、Gen 2 の 3 つの世代領域に分割します。
ここに画像の説明を挿入します
マーク アンド クリア アルゴリズムを使用して、各反復でガベージが削除されます。ヒープは 3 つの世代領域に分割され、対応する GC には 、 、 の 3 つのメソッドが# Gen 0 collectionsあり# Gen 1 collectionsます#Gen 2 collections

Gen0 エリアのメモリがしきい値に達するとトリガーされ# Gen 0 collections、生き残ったオブジェクトが Gen1 エリアに入ります。

Gen1 領域のメモリがしきい値に達するとトリガーされ# Gen 1 collections、Gen0 の生き残ったオブジェクトが Gen1 に配置され、Gen1 の生き残ったオブジェクトが Gen2 に配置されます。

Gen2 領域のメモリがしきい値に達するとトリガーされ、# Gen 2 collectionsGen0 の生き残ったオブジェクトは Gen1 に配置され、Gen1 の生き残ったオブジェクトは Gen2 に配置され、Gen2 の生き残ったオブジェクトの位置は変更されません。

Gen 0 と Gen 1 は比較的小さく、これら 2 つの世代を合わせた経過時間は常に約 16M です。Gen2 のサイズはアプリケーションによって決まり、数 G に達する場合もあるため、第 0 世代と第 1 世代の GC のコストは非常に低くなります。第 2 世代 GC のコストは非常に低く、fullGC と呼ばれ、通常は非常に高価です。世代 0 と世代 1 の GC の大まかな計算は数ミリ秒から数十ミリ秒で完了しますが、世代 2 のヒープが比較的大きい場合、フル GC には数秒かかる場合があります。一般に、.NET アプリケーションの実行中の世代 2、世代 1、および世代 0 の GC の頻度は、およそ 1:10:100 になるはずです。

Lua と同様、オブジェクトが GC によってクリーンアップされると、ファイナライザー関数が呼び出されます。

大きな物体と小さな物体

GC はオブジェクトを小さいオブジェクトと大きいオブジェクトに分離します。オブジェクトが大きい場合、そのプロパティの一部は、オブジェクトが小さい場合よりも重要になります。たとえば、大きなオブジェクトを圧縮する (つまり、メモリ内のオブジェクトをヒープ上の別の場所にコピーする) と、かなりのコストがかかります。したがって、ガベージ コレクターはラージ オブジェクトをラージ オブジェクト ヒープ (LOH) に配置します。

オブジェクトのサイズが 85,000 バイト以上の場合、そのオブジェクトはラージ オブジェクトとみなされます。この数値は、パフォーマンスの最適化に基づいて決定されます。オブジェクト割り当て要求が 85,000 バイト以上の場合、ランタイムはそれをラージ オブジェクト ヒープに割り当てます。大きなオブジェクトは、第 2 世代の収集中にのみリサイクルできるため、第 2 世代に属します。

CLR がロードされると、GC は 2 つの初期ヒープ セグメントを割り当てます。1 つは小さなオブジェクト用 (スモール オブジェクト ヒープ、つまり SOH)、もう 1 つは大きなオブジェクト用 (ラージ オブジェクト ヒープ、LOH) です。SOH の場合、GC によって処理されなかったオブジェクトは次の世代に昇格されます。LOH が処理されていない場合でも、LOH は第 2 世代に属しており、次の世代に昇格することはありません。また、ラージ オブジェクト ヒープでは圧縮が実行されないため、リサイクル後に生成されるメモリ フラグメントのサイズが 85000 バイト未満の場合、このフラグメントはこのプログラムのライフ サイクルで再度使用することはできません。
ここに画像の説明を挿入します


.NET の GC メカニズムには 2 つの問題があります。

まず、GC ではすべてのリソースを解放することはできません。管理されていないリソースを自動的に解放することはできません。

第 2 に、GC はリアルタイムではないため、システム パフォーマンスにボトルネックや不確実性が生じます。

GC はリアルタイムではないため、システム パフォーマンスにボトルネックや不確実性が生じます。したがって、IDisposable インターフェイスでは、プログラマがアンマネージ リソースを解放するために明示的に呼び出すために使用する Dispose メソッドを定義します。using ステートメントを使用すると、リソース管理が簡素化されます。

おすすめ

転載: blog.csdn.net/milu_ELK/article/details/132105268