ホームページ  >  記事  >  バックエンド開発  >  C# 値型と参照型のメモリ管理

C# 値型と参照型のメモリ管理

巴扎黑
巴扎黑オリジナル
2016-12-20 11:50:411757ブラウズ

このブログでは、ソフトウェア開発プロセス中のパフォーマンスを向上させる方法に焦点を当てます。これは、ソフトウェア開発または研究開発プロセスにおける根深い問題です。この記事では、主に、ソフトウェア コードを作成するプロセスでの計算の仕組みを、メモリ割り当てとメモリ リサイクルという 2 つの側面から説明します。ここではメモリ管理のプロセスと方法を理解することで、メモリ管理に注目し、今後のソフトウェア開発に役立てることができます。

値の型には、int、float、double、bool、構造体、参照、オブジェクト インスタンスを表す変数が含まれます

参照の型には、クラスと配列、より特殊な参照型である文字列、オブジェクト

一般的には、値の型のストレージ (スタック内)クラスの値フィールド、クラスの参照フィールド、配列の要素など、参照に含まれる値型は除外されます。これらは参照とともに規制ヒープに格納されます。なぜヒープが規制ヒープに格納されるのか。制御ヒープについては、以下で詳しく説明します。

いくつかの概念:

仮想メモリ: 32 ビット コンピューターでは、各プロセスには 4G の仮想メモリがあります。

規制ヒープ (マネージドヒープ): 誰によって規制されていますか?もちろん、ガベージコレクターはガベージコレクターです。それを管理する方法については後述します。

役に立たないユニットコレクター: ガベージコレクターは、マネージドヒープの圧縮、参照アドレスの更新に加えて、マネージドヒープの情報リストなども維持します。

値型の格納に関しては、まず次のコードを見てください:

{

int age=20;

double給与=2000;

}

上で定義した2つの変数int ageはコンパイラに伝えます。年齢値を保存するために 4 バイトのメモリ領域を割り当てます。年齢値はスタックに保存されます。スタックは先入れ後出しのデータ構造であり、上位アドレスから下位アドレスに向かってデータを格納します。コンピュータのレジスタはスタック ポインタを保持しており、このポインタは常にスタックの一番下にある空き領域のアドレスを指します。int 型の値を定義すると、変数が値を超えるとスタック ポインタが 4 バイトのアドレスだけ減分されます。スコープ、スタック ポインタはそれに応じてアドレスを 4 バイトずつインクリメントします。したがって、スタックのパフォーマンスは非常に高くなります。

参照型のストレージを見てみましょう。まずコードを見てみましょう:

{

Customer customerA;

customerA=new Customer() //Customer インスタンスが 32 バイトを占有していると仮定します

}

上記のコード 最初の行は、最初に Customer 参照を宣言します。参照の名前は customerA で、スタック上にこの参照用の記憶領域を割り当てます。記憶領域のサイズは 1 つの参照のみを格納するため、4 バイトです。指す参照は、Customer インスタンスが格納されるスペース アドレスです。この時点では、customerA は特定のスペースを指すのではなく、スペースを割り当てるだけであることに注意してください。

2 行目の実行中、.net 環境はマネージド ヒープを検索して、クラスのインスタンスに割り当てられた最初の未使用の連続 32 バイト領域を見つけ、customerA がこの領域の先頭位置を指すように設定します。 (ヒープ スペースは低いものから高いものへと使用されます)。参照変数がスコープ外になると、スタック内の参照は無効になりますが、インスタンスはガベージ コレクターによってクリーンアップされるまでマネージド ヒープ内に残ります。

ここを注意深く読んでいる人は、参照型を定義するとき、オブジェクトを格納するのに十分な大きさのメモリ領域を見つけるためにヒープ全体を検索する必要があるのでしょうか?これは非常に非効率的でしょうか?十分な連続スペースがない場合はどうすればよいですか?今回は「監護権」についてです。ヒープはガベージ コレクターによって管理され、ガベージ コレクターが実行されると、.net は解放できるすべてのオブジェクトを解放し、他のオブジェクトを圧縮してから、すべての空き領域を結合してヒープの先頭に移動して連続ブロックを形成します。同時に他のモバイルオブジェクトへの参照を更新します。別のオブジェクト定義がある場合は、適切なスペースをすぐに見つけることができます。マネージド ヒープはスタックと同様に動作し、ヒープ ポインタを通じて領域を割り当て、再利用するようです。

上記は、.net のメモリ領域割り当ての管理プロセスと方法について説明しました。次に、メモリのリサイクル プロセスについて説明します。リソースのクリーンアップに関しては、2 つの概念と 3 つの方法について言及する必要があります。

2 つの概念は、マネージド リソースとアンマネージド リソースです。

マネージド リソースは .net Framework の CLR (Common Language Runtime) によって管理されます。アンマネージド リソースは CLR (Common Language Runtime) によって管理されません。

3 つのメソッドは、Finalize()、Dispose()、Close() です。

1. Finalize() は、アンマネージ リソースをクリアするデストラクター メソッドです。

クラスで定義:

public ClassName{

~ClassName()

{

//管理されていないリソースをクリーンアップします(ファイルやデータベース接続を閉じるなど)

}

}

Its機能 はい:

1. 運用上の不確実性。

ガベージコレクターによって管理され、ガベージコレクターが動作すると、このメソッドが呼び出されます。

2. 高パフォーマンスのオーバーヘッド。

ガベージ コレクターの仕組みは、オブジェクトが Finalize() メソッドを実行する場合、ガベージ コレクターが最初にそれを実行するとき、そのオブジェクトは特別なキューに置かれ、2 回目の実行まで削除されません。

3. 定義と呼び出しは表示できず、定義はデストラクターメソッドの形式になっています。

上記の特性に基づいて、クラスが本当に必要とする場合、または他の 2 つのメソッドと組み合わせて使用​​する場合を除き、Finalize() メソッドを実行しないことが最善です。

2. Dispose() メソッドは、マネージド リソースとアンマネージド リソースを含む、クリアする必要があるすべてのリソースをクリアできます。

は次のように定義されます:

public void Dispose()

{

//クリーンアップする必要があるリソース (マネージドリソースとアンマネージドリソースを含む) をクリーンアップします

System.GC.SuppressFinalize(this) //この文は非常に重要です。その理由については以下で説明します。

}

その特徴:

1. リソースを解放するには、クライアント コードでこのメソッドを明示的に呼び出す必要があります。

2. 最初の理由により、通常はバックアップが必要であり、このバックアップは通常デストラクター メソッドによって再生されます。

3. このメソッドを定義するクラスは、IDisposable インターフェイスを継承する必要があります。

4. 構文キーワード using は、Dispose() を呼び出すことと同じです。using を使用すると、デフォルトで Dispose() メソッドが呼び出されます。したがって、using を使用するクラスも IDisposable インターフェイスを継承する必要があります。

Dispose() メソッドはより柔軟で、リソースが不要になったときにすぐに解放します。これはリソースの最終的な破棄であり、これを呼び出すことは、オブジェクトが最終的に削除されることを意味します。

3. Close() メソッド。リソースのステータスを一時的に破棄し、将来使用される可能性があります。通常、管理されていないリソースを処理します。

は次のように定義されます:

public viod Close()

{

//ファイルやデータベース接続を閉じるなど、管理されていないリソースのステータスを設定します

}

その機能:

管理されていないリソースのステータスを設定しますリソース設定。通常はファイルまたはデータベース接続を閉じます。


以下は、コードを使用して各部分の機能を示す包括的で古典的な例です: (トラブルを避けるために、この例はインターネットから作成したものです。問題を説明するだけで、どう思いますか。このような古典的でわかりやすいコードを書いてくれたコードのオリジナルの作者に感謝する必要があります。

Dispose(true);

System.GC.SuppressFinalize(this);

//上記のコード行は、「ガベージ コレクター」がこのクラスのデストラクター メソッドを呼び出すのを防ぐためです

// " ~ResourceHolder() "

// なぜそれを防ぐ必要があるのでしょうか。なぜなら、ユーザーが Dispose( を呼び出すことを覚えているからです。 ) メソッドを使用すると、

// 「ガベージ コレクター」は「アンマネージ リソース」を再度「不必要に」解放する必要はありません

/ / ユーザーがそれを呼び出したことを覚えていない場合は、「ガベージ コレクター」に「不必要に」手伝ってもらいましょう" ^_^

// 上で言ったことを理解できなくても大丈夫です。以下にさらに詳しい説明があります!

}

protected virtual void Dispose(bool disposing)

{

if (破棄)

{

// これは、「管理リソース」をクリーンアップするためのユーザー コード スニペットです。

}

// これは、「管理されていないリソース」をクリーンアップするためのユーザー コード スニペットです。デストラクタ メソッドの実際の実行コードは次のとおりです。クライアント コードが Dispose() メソッドの呼び出しを忘れないように、バックアップが作成されています。

}

~ResourceHolder()

{

Dispose(false) // これは「管理されていないリソース」をクリーンアップするためのものです

}

}

;上記のコードは、次の説明を必ずよく読んでください。古典的なコードです。読まないと後悔します。

ここで、ユーザーはメソッド Dispose(bool) ではなくメソッド Dispose() を呼び出す必要があることを明確にしておく必要があります。ただし、ここで実際にリリース作業を実行するメソッドは Dispose() ではなく、Dispose(bool) です。 ! なぜですか? コードをよく見てください。Dispose() では、パラメータが「true」の場合に、すべてのマネージド リソースとアンマネージド リソースがクリーンアップされることを覚えておく必要があります。先ほど「 デストラクター メソッドはアンマネージ リソースを解放するために使用されます。」と言いました。では、Dispose() でアンマネージ リソースを解放する作業を完了できるので、デストラクター メソッドは何に使用されますか? 実際、デストラクター メソッドの役割は次のとおりです。ただの「バックアップ」です!

なぜですか?

正式に言えば、「IDisposable」インターフェイスを実装するクラスは、プログラマがコード内でこのクラスのオブジェクト インスタンスを使用する限り、遅かれ早かれこのクラスの Dispose() メソッドを同時に呼び出す必要があります。マネージド リソースが使用されている場合、アンマネージド リソースも解放する必要があります。残念なことに、アンマネージド リソースを解放するコードがデストラクター メソッドに配置されている場合 (上記の例は "~ResourceHolder()" に対応します)。その場合、プログラマーはこのセクションを呼び出したいとします。 コードを解放することは不可能です (デストラクター メソッドはユーザーが呼び出すことができず、システム、正確には「ガベージ コレクター」によってのみ呼び出すことができるため)。上記の例が「アンマネージ リソースのユーザー コードをクリーンアップする」理由を知ってください。「Section」が ~ResourceHolder() ではなく Dispose(bool) にあるのです。プログラマーがこのメソッドを呼び出すのを忘れた場合、マネージド リソースには問題ありません。遅かれ早かれ、それらを収集するための「ガベージ コレクター」が登場します (しばらく遅れるだけです)。しかし、アンマネージド リソースについてはどうでしょうか。 CLR の制御下にないので、占有しているアンマネージ リソースは決して解放できないのでしょうか? もちろん、Dispose() を呼び出すのを忘れた場合でも、ガベージ コレクター" は、アンマネージ リソースを解放するために "破棄メソッド" も呼び出します! (もう 1 つナンセンスですが、プログラマが Dispose() を呼び出すことを覚えている場合、コード "System.GC.SuppressFinalize(this);" によって "つまり、プログラマが Dispose() メソッドを呼び出すのを忘れることを心配する必要はありません。


声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。