概要

前回は章をすっ飛ばして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 の with にあたるハナシ

  • IDisposable を実装しているオブジェクトは、使い終わったとき Dispose を実行すること。 Dispose は片付けるって意味だ。
    • あー、ハイハイ、何のハナシがくるかわかるぜ。これは Python の close と同じハナシだから、次にお前は with にあたるハナシをするっ。
  • Dispose を直接呼び出すのは自由だけど usingforeach を使えば自動で呼び出される。
// こういうふうに書いたとき……
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 から派生している。

『プログラミング C#』 その6 継承 - 全クラスの基底クラス object

 

小休憩

  • ヒープブロックを割り当てるのはランタイムの役目で、回収するのがガベージコレクタの役目。
  • ガベージコレクションのタイミングは、オブジェクトが到達不能(生存期間終了)になったとき。
  • 削除の瞬間にファイナライゼーションが起こるけど、デフォルトでは実行保証がない。
  • IDisposable 継承オブジェクトは使い終わったら Dispose すること。
    • ただしそんなのメンドくせーから実際は using を使うこと。

フツーに Python にも応用できる内容だった。 Python のガベージコレクタも同じような仕様なんかな? サンプルコードとか見ると using あたりがざっくり使われていて、半ばおまじないのように書いちゃってるから、その内実がわかったのは嬉しいね。