2.5 ガベージコレクション

Lua は自動的なメモリ管理を行います。これは新しいオブジェクトに対するメモリの確保や必要無くなったオブジェクトに対するメモリの開放をユーザーが行う必要がないことを意味します。Lua は自動的なメモリ管理にガベージコレクタ (garbage collector) を使います。文字列・テーブル・ユーザーデータ・関数・スレッド・内部で使われるデータ構造など、Lua が使用する全てのメモリが自動的なメモリ管理の対象です。

あるオブジェクトがプログラムの通常の実行において今後アクセスされないことをガベージコレクタが確認したとき、そのオブジェクトは死亡した (dead) とみなされます。(ファイナライザは死んだオブジェクトを蘇生できる (§ 2.5.3) ので、この一文における「通常の実行」はファイナライザを含みません)。ここでオブジェクトの死亡をガベージコレクタが確認するタイミングがプログラマーが予想するそれと異なる可能性があることに注意してください。保証されているのは、プログラムの通常の実行でアクセスできるオブジェクトがコレクトされないこと、そして Lua からはアクセスできないオブジェクトはいずれコレクトされることだけです (ここでオブジェクトに “アクセスできない” とは、そのオブジェクトを指す生きているオブジェクトあるいは変数が存在しないことを言います)。Lua は C コードに関する知識を全く持たないので、レジストリ (§ 4.3) を通してアクセス可能なオブジェクトはコレクトされません。グローバル環境 (§ 2.2) もコレクトされないオブジェクトの一つです。

Lua のガベージコレクタ (GC) の動作モードにはインクリメンタルモード (incremental mode) と世代別モード (generational mode) の二つがあります。

デフォルトの GC モードをデフォルトのパラメータで使えば大部分のユーザーはそれで十分です。ただメモリの確保と開放に多くの時間を費やすプログラムでは、設定を変えると最適化できる場合があります。GC の動作はプラットフォームおよび Lua リリース間でポータブルではないことに注意してください。このため最適な設定もポータブルではありません。

GC モードやパラメータの変更は C の lua_gc または Lua の collectgarbage を使って行います。これらの関数を使って GC を直接操作することもできます (GC を停止/再開させるなど)。

2.5.1. インクリメンタルガベージコレクション

インクリメンタルモードでは、GC サイクルごとにマークアンドスウィープを使ったガベージコレクトがプログラムの実行に割り込んだ小さなステップで少しずつ実行されます。このモードでは三つの数値がガベージコレクションのサイクルを管理します: ポーズ (pause) , ステップ倍率 (step multiplier) , ステップサイズ (step size) です。

ポーズ n は新しいサイクルを始めるまでにコレクタがどれくらい待つかを制御します。コレクタが新しいサイクルを開始するのは、メモリ使用量が前回のコレクションを終えた時点における使用量の n% になったときです。n に大きな値を設定するとコレクタが起動しにくくなり、n を 100 以下にすれば新しいサイクルを始めるにあたってコレクタが一切待たなくなります。また n を 200 とすれば、コレクタはメモリの総使用量が倍になるのを待ってから新しいサイクルを始めます。デフォルトの値は 200 で、最大値は 1000 です。

ステップ倍率はメモリアロケーションに対するコレクタの相対速度を制御します。つまりアロケートされたメモリのサイズに対するマークあるいはスウィープを行う要素の数がこの値に比例します。大きなステップ倍率を設定するとコレクタが積極的になりますが、インクリメンタルステップのサイズが大きくなります。100 より小さい値を使うべきではありません。コレクタが非常に遅くなり、サイクルが終わらなくなる可能性があるからです。デフォルトの値は 100 で、最大値は 1000 です。

ステップサイズはインクリメンタルステップのサイズを制御します。具体的には、この値はステップが始まるまでにインタープリタがアロケートするメモリのバイト数を表します。このパラメータは実際の値の対数であり、ステップサイズが n のときインタープリタは連続するステップの間に 2 バイトのメモリを確保し、それだけの処理をステップで行います。60 などの大きな値を設定すれば、コレクタは stop-the-world (インクリメンタルでない) コレクタとなります。デフォルトの値は 13 であり、このとき 8 キロバイトのステップが実行されます。

2.5.2. 世代別ガベージコレクション

世代別モードのコレクタは最近作成されたオブジェクトだけを走査するマイナーコレクション (minor collection) を頻繁に行います。マイナーコレクションをした後のメモリ使用量が基準よりも大きければ、コレクタは全てのオブジェクトを走査する stop-the-world なメジャーコレクション (major collection) を行います。世代別モードのガベージコレクタは二つのパラメータを持ちます: マイナー倍率 (minor multiplier) とメジャー倍率 (major multiplier) です。

マイナー倍率はマイナーコレクションの頻度を制御します。マイナー倍率が x のとき、新しいマイナーコレクションは前回のメジャーコレクション直後を基準としてメモリ使用量が x% 増えたときに実行されます。例えばマイナー倍率が 20 なら、メモリ使用量が前回のメジャーコレクション直後の 120% になったときにマイナーコレクションが実行されます。デフォルトの値は 20 で、最大値は 200 です。

メジャー倍率はメジャーコレクションの頻度を制御します。メジャー倍率が x のとき、新しいメジャーコレクションは前回のメジャーコレクション直後を基準としてメモリ使用量が x% 増えたときに実行されます。例えばメジャー倍率が 100 なら、メモリ使用量が前回のメジャーコレクション直後の 200% になったときにメジャーコレクションが実行されます。デフォルトの値は 100 で、最大値は 1000 です。

2.5.3. ガベージコレクションに関するメタメソッド

テーブルの GC に関するメタメソッドは Lua から設定でき、C を使えばフルユーザーデータ (§ 2.4) にも設定できます。こういったメタメソッドにはファイナライザ (finalizer) という名前が付いており、対応するテーブルまたはユーザーデータがガベージコレクタによって死んだと確認されたときに呼ばれます。ファイナライザを使うと Lua の GC と外部リソースの管理 (ファイル・ネットワーク・データベース接続のクローズや独自のメモリ解放処理など) を結び付けることができます。

コレクト時に呼ぶべきファイナライザがあるオブジェクト (テーブルまたはユーザーデータ) がある場合には、ファイナライザの存在をマークする必要があります。__gc という文字列を添え字とするフィールドを持つテーブルをオブジェクトのメタテーブルに設定すると、そのオブジェクトに対するファイナライザの存在をマークできます。__gc フィールドを持たないメタテーブルを設定してからメタテーブルに __gc フィールドを作成してもマークは行われないことに注意してください。

マークされたオブジェクトが死んだとしても、ガベージコレクタによってすぐにコレクトされるわけではありません。まず Lua はそういったオブジェクトをとあるリストに加え、コレクトの後にそのリストを走査し、リスト内の各オブジェクトに対して __gc メタメソッドを確認します。もし __gc があれば、オブジェクトを引数としてそれを呼びます。

GC サイクルが終わるたびに、サイクル中にコレクトされたオブジェクトのファイナライザがマークの順序と逆順で呼ばれます。つまり最初に呼ばれるファイナライザはプログラム中で最後にマークされたオブジェクトです。ファイナライザの実行は通常のコード実行中の任意のタイミングで行われます。

ファイナライザではコレクトされるオブジェクトが利用できなければならないので、Lua によって蘇生されます。通常この蘇生は一時的で、オブジェクトのメモリは次の GC サイクルで解放されます。しかしファイナライザがオブジェクトをグローバルな場所 (グローバル変数など) に保存した場合には、この蘇生は永続的になります。またファイナライザが引数として受け取ったオブジェクトをもう一度マークすれば、そのオブジェクトが死ぬ次のサイクルでそのファイナライザがもう一度呼ばれます。いずれの場合でも、GC サイクルでオブジェクトのメモリが解放されるのは、オブジェクトが死んでいてかつファイナライズのためにマークされていないときだけです。

lua_close でステートを閉じると、Lua はファイナライズのためにマークされた全てのオブジェクトに対してファイナライザを呼び出します (順番はマークの逆順です)。この処理中にファイナライザがオブジェクトをマークしたとしても、そのマークは意味を持ちません。

ファイナライザでは yield できませんが、これを除けばファイナライザは何でも行えます。エラーの送出や新しいオブジェクトの生成も可能であり、GC の実行さえできます。しかしファイナライザは予測できないタイミングで実行されるので、関連するリソースを開放するための最小限の処理だけを行うのが良い習慣です。

ファイナライザの実行中に送出されるエラーは警告となり、伝播されません。

2.5.4. 弱参照テーブル

弱参照テーブル (weak table) は弱参照 (weak reference) を要素とするテーブルです。弱参照はガベージコレクタによって無視されます。つまり、あるオブジェクトに対する参照が弱参照だけである場合、ガベージコレクタはそのオブジェクトをコレクトします。

弱参照テーブルはキーとバリューの片方または両方を弱参照にできます。バリューが弱参照のテーブルではバリューはコレクトされますが、キーはコレクトされません。キーとバリューの両方が弱参照のテーブルではキーとバリューの両方がコレクトされます。いずれの場合でも、キーまたはバリューがコレクトされるとそのペアはテーブルから削除されます。テーブルが弱参照を持つかどうかはメタテーブルの __mode フィールドで管理されます。このメタバリューが定義されているなら、その値は文字列 "k", "v", "kv" のいずれかでなくてはなりません。"k" はキーが弱参照であることを表し、"v" はバリューが弱参照であることを表し, "kv" はキーとバリューの両方が弱参照であることを表します。

弱参照のキーとそうでないバリューを持つテーブルを短命テーブル (ephemeron table) と呼びます。短命テーブルではキーが到達可能なときに限ってバリューも到達可能となります。例えばキーに対する参照がバリューを通じたものだけなら、そのペアは削除されます。

テーブルの弱参照性の変更が効果を持つのは次のコレクトサイクルからです。例えばテーブルの弱参照性を弱めたとしても、変更が効果を持つまでの間に Lua はそのテーブルの要素を削除する可能性があります。

弱参照テーブルから削除されるのは明示的な構築処理があるオブジェクトだけです。数値や C 関数といった値は GC の対象とならないので、弱参照テーブルから削除されることはありません (対応するキーまたはバリューがコレクトされた場合は除く)。文字列は GC の対象ですが、明示的な構築処理を持たず、等価性は値を使って判定されます。つまり文字列はオブジェクトというより値に近いので、弱参照テーブルから削除されません。

蘇生されたオブジェクト (ファイナライザを実行中のオブジェクトまたはファイナライザからのみアクセス可能なオブジェクト) は弱参照テーブルで特別に振る舞いをします。こういったオブジェクトはファイナライザを実行する前に弱参照テーブルのバリューから削除されますが、キーから削除されるのはファイナライザを実行した次のコレクト (オブジェクトが実際に解放されるタイミング) です。この振る舞いにより、コレクトされるオブジェクトに弱参照テーブルで結び付いたプロパティにファイナライザからアクセスできるようになります。

コレクションサイクル中に蘇生されたオブジェクトに弱参照テーブルが含まれる場合、それが適切にクリアされるのが次のサイクルとなる可能性があります。