読者です 読者をやめる 読者になる 読者になる

【翻訳】みんなGCについて誤解している(Old New Thing)

翻訳対決です。質問ではない。
【翻訳】ガーベージコレクションについてよくある誤解 - みっどメモ

Everybody thinks about garbage collection the wrong way - The Old New Thing - Site Home - MSDN Blogs

 CLRウィーク2010にようこそ。今年度のCLRウィークは例年よりも哲学的です。

 もしGCとは何か、誰かに聞いたとしたら、きっと「GCは実行環境が不要になったメモリを回収する仕組みのことであり、メモリを根から辿り、到達可能なオブジェクトを識別することで実現される…」といった答えが返ってくるだろう。

 しかしこれは手段と目的を混同している。これはあたかも消防士を「赤いトラックを運転してお水を撒くお仕事」だと言うようなものだ。消防士が何をするかの説明にはなっているかもしれないが、これでは仕事の要点を見失っている(火を消す、消防活動)。

 GCとは「無限のメモリを持つコンピュータのシミュレーションをすること」である。それ以外の説明は手段にすぎない。そして一般にその手段として「消えてもプログラムに影響しないメモリを回収する方法」が用いられる。

 GCの真の定義から、以下の結論が直ちに導ける:

 「プログラムが実行時に使えるRAMの量が、必要とするメモリより大きければ、何もしないGCも正しいGCである。」

 この仮定のもとでは、メモリマネージャはいつでもプログラムが必要とするメモリを確保でき、失敗しない。プログラムが必要とするメモリより多くのRAMを搭載したコンピュータは、実質的に無限のRAMを持っているのと同じことであり、シミュレーションは必要ない。

 この定理は自明かもしれないが実用的だ。なぜなら、何もしないGCの振る舞いは簡単に解析することができ、普段慣れ親しんでいるGCとは極めて異なった振る舞いをするからだ。たとえば、何もしないGCから以下のような結論を導き出せる:

 「正しく書かれたプログラムは、ファイナライザが実行されることを仮定しない」

 証明は単純だ。プログラムが必要とするメモリより多くのRAMを搭載したコンピュータで、プログラムを走らせる。このとき、何もしないGCは正しいGCだ。よって、何もしないGCは何も回収しないので、ファイナライザは一度も走らない。

 GCは無限のメモリをシミュレーションする。しかし、無限のメモリをもってしてもできないことがある。他のプログラム(そしてそのプログラム自身)に見える影響を与える事柄である。例えば、ファイルを排他モードで開いたら、閉じられるまで、そのファイルは他のプログラムからはアクセスできなくなる(場合によってはそのプログラムの他の部分からも)。SQLサーバーにコネクションを張ったら、閉じるまでそのサーバーのリソースを浪費することになる。もしコネクションの数が巨大なら、コネクション数の上限に達して、それ以上接続できなくなるかもしれない。もしリソースを明示的に解放せず、プログラムが「無限メモリのマシン」上で走っていたら、リソースは溜め込まれるばかりで二度と解放されないことになる。

 つまり何が言いたいかというと: ファイナライザに頼ったプログラムを書くな。ファイナライザはセーフティーネットであって、リソース回収のための手段ではない。もしリソースを使い終わったら、そのオブジェクトのCloseやDisconnectのようなクリーンアップメソッドを呼び出して解放する必要がある(IDisposableはこの規約を明示的に示すインターフェイスだ)。

 さらに、正しく書かれたプログラムは、実行時にファイナライザが走ることを仮定できないだけでなく、終了時にファイナライザが走ることも仮定できない。.NET Frameworkはファイナライザを全て実行しようと試みる。しかし、不良ファイナライザがある場合、.NET Frameworkはファイナライザの実行を諦め、無視することがある。そしてこれは貴方に一切落ち度がなくても起こりうる。例えば、ファイナライザでネットワークリソースのハンドルを解放しようとして、回線の問題で2秒以上かかったら、.NET Frameworkはプロセスを止めてしまう。よって以上より、.NET Frameworkのある場合について

 「正しく書かれたプログラムは、ファイナライザが実行されることを仮定しない」

は特に当てはまることがわかる。

 以上の知識から、あなたなら顧客の以下の問題を解けるだろう(用語の混乱は原文通り)。

「私の手元にXmlDocumentを使うクラスがあります。このクラスがスコープを抜けた後、私はファイルを削除したいのですが、『System.IO.Exception: The process cannot access the file 'C:\path\to\file.xml' because it is being used by another process』と怒られてしまいます。プログラムが終了すると、ロックは消えてしまうみたいです。ファイルのロックを回避したいのですがどうすればよいでしょうか?」

 このメールの追伸は役に立つだろうか?

「同僚はXmlDocumentを使い終わったらnullをセットすればいいと言ってきましたが、スコープを抜けた時も同じように動くべきじゃないんですか?」

おまけ: ファイナライザは奇妙だ。なぜなら、「GCの裏側」で動くからだ。WeakReference GCHandleやSystem.GC自身など、「GCレベル」で動くクラスもたくさんある。これらのクラスを正しく使うにはこれらがGCとどう関わっているかを理解している必要がある。これらについてはそのうち解説する。