概要
前回は章をすっ飛ばして20章(『プログラミング C#』 その20 ASP.NET)を読んでみたけれど、また戻ってきたぞ。オブジェクトの生存期間、という章タイトルだけども。あー? オブジェクトの生存期間なんて、スコープの内部ダロ、知ってんだよ!(フラグ)
じゃあ流し読みしていこう。
流し読みノート
自分でメモリ管理する必要がない理由
- CLR が GC(ガベージコレクタ)を提供しているから。
- CLR は1章 C# の基礎で読んだから覚えているぞ、 C# のランタイムだね。 Common Language Runtime。
- GC は、オブジェクトがもう利用されていないときを見つけて、そのオブジェクトが占有しているメモリを回収する奴だ。
- どうでもいいんだけど GC っていうとどうしても「グランド・キャニオン」って読んでしまう。ナゼだ。このノートは自分なりのモノなので、ガベージコレクタと表記することにするぜ。
というわけでフツーこのメモリだのなんだのは気にしなくていいんだけど、ガベージコレクタの効果をなくす利用パターンが存在するのだ。間違ってそれを踏んじまうと宝の持ち腐れだから、ガベージコレクタの動作を知っておき、ジャマをしないようにしようってのがこの章の目的だ。
ガベージコレクタがやることをもうちょっと詳しく
new
のたびに CLR はオブジェクトにヒープブロックを割り当てる。- メモリは分かるけれどヒープブロックって何やねんいきなり。
- ヒープメモリっていう、メモリの一種があるっぽい。任意に確保や開放を動的に行えるメモリだ。あー、これがぼくが「メモリ」だと思っていたものだ。ぼくがメモリだと思っていたものは実はヒープメモリだったのだろう。そしてオブジェクトの大きさに合わせて確保したヒープメモリが、ヒープブロックかな。
- オブジェクトには型があり、型をみるとどれくらいのヒープブロックが必要かわかる。ほとんどの型のサイズは一定だからだ。だから割り当てることが可能なんやね。
- ただし
string
と配列は除くが。
- ただし
- ガベージコレクタは、ヒープブロックを開放するタイミングを決定してくれる。あと「ヘッダ」も追加する。
- オブジェクトの型へのポインタを含む情報みたい……。この情報が、
GetType
メソッドが呼ばれたとき使われるようだ。
- オブジェクトの型へのポインタを含む情報みたい……。この情報が、
- ヒープブロックを開放するタイミングは、そのオブジェクトが到達不能とみなされたときだ。それまでをオブジェクトの生存期間という。
- 生存状態が終わるのは、そのオブジェクトが最後に利用されている場所。
- おや……ではオブジェクトの生存期間はスコープの内部ってわけじゃなかったみたいだ。(フラグ回収)
- コレクション自体が到達可能のとき、コレクション内部のオブジェクトはすべて到達可能とされるため、使われてなくても生存してしまう(コレクションはガベージコレクタを欺く)。だからコレクションについてはプログラマが削除をちゃんとしないとダメ。
- ガベージコレクションのあと、 CLR がヒープブロックをヒープの先頭にコピーし、整理する。コンパクションという。図がないとイメージしづらいんだが、バラバラと散らばったブロックを一箇所に集めるってことだね。
- 何がいいかっていうと、ヒープの後ろに空き領域ができるので、新しいヒープブロックの割当が良好に行えるのだ。
- すると次々に生成されたオブジェクトがヒープ内で隣り合わせになる。
- 関連するデータは近くに格納されると最高性能を発揮する(?!)のでこれは都合がいい。最高や。
- CLR はガベージコレクタの振る舞いの調整を自動で行う。回収の契機となる閾値を動的に調整したり。
- 一方で、我々がガベージコレクションモードを変えることもできる。
- ワークステーションモード: タスクが少ないとき用の設計。単一コアならメモリ使用少の nonconcurrent mode、複数コアならメモリ使用中の concurrent mode で動かす。
- サーバモード: メモリ使用多。
- Web.config とか App.config の中に次のように書いて切り替えるらしい。
<!-- サーバ GC を利用する構成にする。 -->
<configuration>
<runtime>
<gcServer enabled="true" />
</runtime>
</configuration>
オブジェクト削除、メモリ回収の瞬間に起こること
- もうすぐ削除されようとしていることをオブジェクトに告げることを finalization という。
- そのタイミングでなんかしたかったらデストラクタを書く。 Finalize というメソッドをオーバーライドしてコンパイルされるメソッドのことだ。
- なんで Finalize を override しないんだ? それはわからん。
public class LetMeKnowMineEnd
{
~LetMeKnowMineEnd()
{
Console.WriteLine("Goodbye");
}
}
- ただしファイナライザの実行は保証されない。(えぇ……)
- 強制的に実行させたかったら
CriticalFinalizerObject
を継承させればいい。 - ファイナライゼーションは到達不能と判断されたオブジェクトを到達可能にするから、メモリ効率が悪い。
- ファイナライズが存在する唯一の理由は「ハンドル」っちゅーものをラップする型を書くため。
- なるほどわからん。たぶん Python でいうところの
open
とかconnection
とかそういうやつ?
- なるほどわからん。たぶん Python でいうところの
Python の with にあたるハナシ
IDisposable
を実装しているオブジェクトは、使い終わったときDispose
を実行すること。 Dispose は片付けるって意味だ。- あー、ハイハイ、何のハナシがくるかわかるぜ。これは Python の
close
と同じハナシだから、次にお前はwith
にあたるハナシをするっ。
- あー、ハイハイ、何のハナシがくるかわかるぜ。これは Python の
Dispose
を直接呼び出すのは自由だけどusing
やforeach
を使えば自動で呼び出される。- ほらな! てか
foreach
もそうだったのか。 - そういえばこないだ自作関数を
with
で使うってハナシを読んだけど(え?! 自作関数を with で使えるって?! contextlib.contextmanager)まだ試せていないなー。どっかで試したい。
- ほらな! てか
// こういうふうに書いたとき……
using (StreamReader reader = File.OpenText("File.txt"))
{
Console.WriteLine(reader.ReadToEnd());
}
// 実はこういうことが起きている。
{
StreadReader rader = File.OpenText("File.txt");
try
{
Console.WriteLine(reader.ReadToEnd());
}
finally
{
if (reader != null)
{
((IDisposable) reader).Dispose();
}
}
}
// using は重ねて書ける。
using (Stream source = File.OpenRead("File.txt"))
using (Stream copy = File.Create("Copy.txt"))
{
source.CopyTo(copy);
}
- クローズしないといけないものはたいてい
using
で書くべきだけどIDisposable
インタフェースをサポートしていなければ使えない。たとえば PowerPoint の COM ベースのオートメーション AI とか。
// こういうふうに書いたとき……
foreach (string file in Directory.EnumerateFiles("temp"))
{
Console.WriteLine(file);
}
// 実はこういうことが起きている。
{
IEnumerator<string> e = Directory.EnumerateFiles("temp").GetEnumerator();
try
{
while (e.MoveNext())
{
string file = e.Current;
Console.WriteLine(file);
}
}
finally
{
if (reader != null)
{
((IDisposable) e).Dispose();
}
}
}
IDisposable
を実装するときは、何回Dispose
されてもいいように作る。- ほんで
Dispose
されたあとは利用禁止。そういうことされそうになったらObjectDisposedException
を使うこと。
値型はヒープの中にないときがある
- 参照型はつねにヒープ内に生存する。
- 値型はそうだったりそうでなかったりする。
- 値型のローカル変数はスタック上に格納される。スタックってなんだ。まあここではヒープではないと思えばいいか。
- ヒープにない
int
は参照型であるobject
に代入されるとき、ボックス化されている。値型をラップして参照型にしているやつだ。
最後のトピックだけよくわかんない。こないだ、 int
の基底クラスは参照型だってハナシあったじゃん。基底が参照型なんだからヒープなんじゃないの? まあいいや。あんまり気になるトコじゃない。
int
とかbool
の値型では、基底型はSystem.ValueType
だ。- びっくりだけど
ValueType
は値型じゃなくて参照型だ。しかもobject
から派生している。
小休憩
- ヒープブロックを割り当てるのはランタイムの役目で、回収するのがガベージコレクタの役目。
- ガベージコレクションのタイミングは、オブジェクトが到達不能(生存期間終了)になったとき。
- 削除の瞬間にファイナライゼーションが起こるけど、デフォルトでは実行保証がない。
IDisposable
継承オブジェクトは使い終わったらDispose
すること。- ただしそんなのメンドくせーから実際は
using
を使うこと。
- ただしそんなのメンドくせーから実際は
フツーに Python にも応用できる内容だった。 Python のガベージコレクタも同じような仕様なんかな? サンプルコードとか見ると using
あたりがざっくり使われていて、半ばおまじないのように書いちゃってるから、その内実がわかったのは嬉しいね。