メモリとリソース管理
こんにちは、naoyaです。
毎月一回のプログラム学習記事の投稿です。
第五回は、「メモリとリソース管理」について、調べたことをまとめていこうと思います。
・.NET Frameworkでのメモリ管理
C#をはじめとした.NET Framework上で動く言語は、メモリ管理
を.NET Frameworkのガベージコレクションに任せることで、管理
の手間を削減している。
一般に、メモリの管理方法には「スタック」と「ヒープ」の2
種類の方法がある。C#では、ローカル変数はすべて「スタック」
上に置かれる。変数が「値型」の場合、値すべてがスタック上に
置かれる。「参照型」の場合、値そのものはヒープ上に置かれ、
そのヒープへの参照情報のみが、スタック上に置かれる。
スタック
データ領域を積み上げていくような形でメモリを管理する方
法。最後に確保したデータ領域を最初に開放する。メモリの管
理が簡単で非常に効率が良いが、あらかじめスコープが分かっ
ているデータにたいしてしかりようできない。
ヒープ
任意サイズのデータ領域を任意の順で確保、解放していく方
式。データを扱うスコープがはっきりしない(いろんな所から
参照されている)場合や、実行時にしかサイズが確定しない場
合、必要サイズがかなり大きい場合などに使う。
スタックのような制限はないが、メモリ上のどこからどこま
でを確保しているか、管理する必要がある。また、新たにデー
タを確保したい場合、空き容量を探す手間がかかる。
領域の確保と開放を繰り返すことで、使用中のヒープがメモ
リ上に散在する状態(断片化)になる。こうなると、空き容量を
探す手間が増え、データの参照の局所性が失われることで、
キャッシュのヒット率が下がり、処理性能が低下する。
ガベージコレクション
ヒープを利用する時、用済みのデータ領域の解放を忘れて、
メモリ上に「ごみ(いつまでたっても解放されない領域)」が
残ってしまうことがある。このような問題を、「メモリ・リー
ク」と呼ぶ。逆に、既に開放している領域を再度解放しようと
する問題を二重解放と呼ぶ。
プログラマが自前でメモリを解放しようとする場合、プログ
ラムが複雑になるにつれ、メモリ・リークや二重解放の発見は
難しくなる。プログラマの目的はヒープの管理ではないのに、
そちらに時間を割くことになるのは良くない。
ヒープの管理を自動化する手法の一つとして、ガベージコレ
クションというものがある。ガベージコレクションは、定期的
に「ごみ」になっている領域がないか調べて、見つけた「ご
み」を解放して回る処理を行う。
値型と参照型
C#の型には大きく分けて2つのタイプがある。一つは「値
型」で、もう一つは「参照型」と呼ばれる。
・値型…変数に直接値が格納される。
・参照型…変数は参照情報を持っていて、実体は別の場所に
ある。
値型は、代入時に値のコピーを受け取り、参照型は値の実態
への参照情報を受けとる。このため、値型はコピー先の値に変
更を加えてもコピー元には何も影響はないが、参照型の場合、
コピー先の値を変えるとコピー元の値も変更される。
値渡しと参照渡し
プログラミング言語での値の受け渡し方法には、値渡しと参
照渡しがある。C#では基本、値渡しになる。refやoutという
キーワードを使うと、参照渡しを行うことができる。
・値渡し:メソッド内の値を変更しても呼び出し元には影響
しない。
・参照渡し:メソッド内の値を書き換えると、呼出し元にも
影響する。
・out:特別な参照渡し。戻り値以外にも値を返したいとき
などに使う。
出力引数
参照渡しを使うと、メソッド内からメソッド外の値を書
き換えることができる。複数の戻り値を返すのに有効であ
るが、refを使う参照引数では、いくつか問題がある。
・呼出元で、特に意味のない値を初期化する必要がある。
→メソッド内で必ず値を上書きするので、無駄。
・メソッド内で代入を忘れてもコンパイルエラーになら
ない。
そこで、戻り値として使いたい場合、outを使って、出
力用の参照引数であることを明示する。outを使うと、
・メソッド呼出前に初期化する必要がない。
・メソッド内で必ず値を割り当てなければならない。
となる。
リソースの破棄
ファイルや周辺機器などのリソースを使用する場合、まずリ
ソースを使用する権利を取得し、操作を行った後、リソース使
用権を破棄する必要がある。
メモリは.NET Frameworkのガベージコレクションが自動的に
管理してくれているが、ファイルはガベージコレクションの対
象外で、明示的な破棄が必要である。
リソースの破棄を怠ると、操作が正しく終了しなかったり、
ほかのプログラムがそのリソースを使用できなくなったりする。
ファイルを確実に閉じるためにはtry~catch~finallyまた
はusingステートメントを使用する。
try~finallyとusingは等価
Resource R = new Resource;
try
{
//処理
}
finally
{
R.Dispose();
}
||
using(Resource R = new Resource)
{
//処理
}
スタックやヒープ、ガベージコレクションについては、名前だけ
知っていましたが、メモリ管理等は特にしたことがないため、それ
ぞれが具体的にどんな役割をしているのかは知りませんでした。ま
た、値型と参照型についても、実際に値が保持される場所などは特
に気にしていなかったため、知りませんでした。今回、それらの意
味などを知ることができてよかったと思います。
参照渡しやリソースの破棄については、開発で使うことが多いの
で、使い方や中身などは知っていました。とはいえ、リソースの破
棄については、ガベージコレクションの具体的な役割について知ら
なかったため、「なぜ毎回閉じなければいけないのか。」と思うこ
ともありました。今回、このリソースの破棄の意味を知ることがで
きて良かったです。
次回(第六回・最終回)は、「非同期処理」について、調べていこ
うと思います(3月中に出したい…)。
3月 7th, 2017 at 5:47 PM
トピックが今風で、毎回楽しみにしています。
> ファイルはガベージコレクションの対象外で、明示的な破棄が必要である
破棄自体はしてくれます。ただ、タイミングが不定(運が悪いとプログラムの終了時まで残る)なので、その場で破棄できる事がわかっているなら人間がコンパイラに教えてやるほうが早い、ということだと思います。
3月 9th, 2017 at 5:51 PM
>stenretniさん
なるほど、破棄自体はしてくれるんですね。
タイミングが不定だから人間が破棄する方がいいってことですか。
ありがとうございます。
3月 9th, 2017 at 7:07 PM
Javaやoracleでは、まれにですけど、
思いもよらないところで「残る」ときがありますから、
気を付けた方が良いと思います。
3月 14th, 2017 at 1:33 PM
>yukiさん
思いもよらない所で、ですか…
気を付けます。