コンピュータサイエンスにおいて、ファイナライザまたはファイナライズメソッドは、一般的に何らかのクリーンアップであるファイナライゼーションを実行する特別なメソッドです。ファイナライザは、オブジェクトの破棄中に、オブジェクトが解放される前に実行され、オブジェクトの作成中に、割り当て後に実行されるイニシャライザと相補的な関係にあります。ファイナライザは、適切な使用が難しく、複雑さが増すため、一部の人からは強く推奨されておらず、代わりに主にdisposeパターン[ 1 ](ファイナライザの問題点を参照)などの代替案が提案されています。
ファイナライザという用語は、オブジェクト指向言語(典型的にはSmalltalk)や関数型言語(典型的にはML)など、ガベージ コレクションを使用するプログラミング言語で主に使われます。これは、決定論的なオブジェクト寿命を持つ言語(典型的にはC++)でファイナライゼーションのために呼び出されるメソッドであるデストラクタとは対照的です。[ 2 ] [ 3 ]これらは一般的に排他的です。言語には、ファイナライザ(自動ガベージ コレクションの場合)またはデストラクタ(手動メモリ管理の場合)のいずれかがありますが、まれにC++/CLIやDのように両方を持つ言語もあり、参照カウント(トレース ガベージ コレクションの代わりに)の場合は用語が異なります。技術的な用途では、ファイナライザはデストラクタを指すためにも使用されることがあります。これらもファイナライゼーションを実行するため、より微妙な区別がなされます(用語を参照)。finalという用語は、継承できないクラスを示す場合もあります。これは無関係です。
ファイナライザーとファイナライゼーション、デストラクタとデストラクションという用語の使い分けは著者によって異なり、時として不明瞭である。
一般的に、デストラクタとはオブジェクトが破棄される際に決定的に呼び出されるメソッドであり、その典型例はC++のデストラクタです。一方、ファイナライザとはガベージコレクタによって非決定的に呼び出されるメソッドであり、その典型例はJavaのfinalizeメソッドです。
参照カウントによるガベージコレクションを実装する言語では、用語が異なり、 Objective-CやPerlなどの言語ではデストラクタを使用し、 Pythonなどの言語ではファイナライザを使用します(仕様上、Python はガベージコレクションされますが、バージョン 2.0 以降のCPython の実装では参照カウントとガベージコレクションが混在しています)。これは、参照カウントによってオブジェクトの寿命が半決定論的になることを反映しています。サイクルの一部ではないオブジェクトは、参照カウントがゼロになると決定論的に破棄されますが、サイクルの一部であるオブジェクトは、別の形式のガベージコレクションの一部として非決定論的に破棄されます。
特定の狭義の技術的な用法では、コンストラクタとデストラクタは言語レベルの用語であり、クラスで定義されたメソッドを意味しますが、イニシャライザとファイナライザは実装レベルの用語であり、オブジェクトの生成または破棄中に呼び出されるメソッドを意味します。したがって、たとえば、 C#言語の元の仕様では、C# はガベージ コレクションされますが、「デストラクタ」という用語が使われていましたが、 Common Language Infrastructure (CLI) の仕様、およびそのランタイム環境の実装であるCommon Language Runtime (CLR) では、「ファイナライザ」という用語が使われていました。これは、C# 言語委員会のメモにも反映されており、その一部には「C# コンパイラはデストラクタを ... [おそらく] インスタンス ファイナライザにコンパイルします」と書かれています。[ 4 ] [ 5 ]この用語は紛らわしいので、C# 仕様のより新しいバージョンでは、言語レベルのメソッドを「ファイナライザ」と呼んでいます。[ 6 ]
この用語の区別をしない別の言語としてDがある。Dクラスはガベージコレクションされるが、そのクリーンアップ関数はデストラクタと呼ばれる。[ 7 ]
ファイナライゼーションは主にクリーンアップ、つまりメモリやその他のリソースの解放に使用されます。手動メモリ管理によって割り当てられたメモリの解放、参照カウントが使用されている場合の参照のクリア(参照カウントのデクリメント)、特にリソース取得が初期化である(RAII)イディオムでのリソースの解放、またはオブジェクトの登録解除などです。ファイナライゼーションの量は言語によって大きく異なり、手動メモリ管理、参照カウント、決定論的なオブジェクトライフタイムを持つ C++ では広範囲にわたるファイナライゼーションが行われる一方、非決定論的なオブジェクトライフタイムを持ち、トレースガベージコレクタで実装されることが多い Java ではファイナライゼーションがほとんど行われないことがよくあります。また、明示的な(ユーザー指定の)ファイナライゼーションはほとんどまたはまったくないが、コンパイラ、インタプリタ、またはランタイムによって実行される暗黙的なファイナライゼーションがかなりある場合もあります。これは、Python のCPythonリファレンス実装や、 Apple のObjective-C実装の自動参照カウントなど、自動参照カウントの場合によく見られます。これらはどちらもファイナライゼーション中に自動的に参照を破棄します。ファイナライザには任意のコードを含めることができます。特に複雑な使用例としては、オブジェクトを自動的にオブジェクトプールに戻すことが挙げられます。
ファイナライゼーション時のメモリ解放は、手動メモリ管理が標準となっている C++ などの言語では一般的ですが、マネージド言語でも、メモリがマネージドヒープ外 (言語の外部) に割り当てられている場合に発生します。Java では、Java Native Interface (JNI) やNew I/OByteBuffer (NIO)のオブジェクトで発生します。後者の場合、ガベージコレクタがこれらの外部リソースを追跡できないため、十分に積極的に回収されず、アンマネージドメモリが枯渇してメモリ不足エラーが発生する可能性があるため、問題が発生する可能性があります。この問題は、ネイティブメモリをリソースとして扱い、後述するdispose パターンを使用することで回避できます。
ファイナライザは、一般的にデストラクタに比べて必要性も使用頻度もはるかに低い。必要性が低いのは、ガベージコレクションによってメモリ管理が自動化されるためであり、使用頻度が低いのは、ファイナライザが一般的に決定論的に実行されないためである。ファイナライザは適切なタイミングで呼び出されるとは限らず、場合によっては全く呼び出されないこともあり、実行環境を予測することはできない。そのため、決定論的に実行する必要のあるクリーンアップは、代わりに別の方法、多くの場合、dispose パターンによる手動処理で行う必要がある。特に、Java と Python はファイナライザが必ず呼び出されることを保証していないため、クリーンアップにファイナライザを頼ることはできない。
ファイナライザはプログラマが実行を制御できないため、ごく単純な操作以外では使用しないことが推奨されます。特に、デストラクタでよく行われる操作は、ファイナライザには適していません。よくあるアンチパターンとして、ファイナライザをデストラクタのように記述してしまうことが挙げられますが、ファイナライザとデストラクタには違いがあるため、これは不要で非効率的です。これは、リソース取得は初期化(RAII)イディオムに従って、デストラクタがC++の慣用的なコードで多用されるため、C++プログラマの間で特に多く見られます。
ファイナライザを使用するプログラミング言語には、C++/CLI、C#、Clean、Go、Java、JavaScript、Pythonなどがあります。構文は言語によって大きく異なります。
finalizeJavaでは、ファイナライザはメソッドをオーバーライドするメソッドですObject.finalize。[ 8 ]
JavaScriptでは、FinalizationRegistryを使用すると、オブジェクトがガベージコレクションされる際にコールバックを要求することができます。
Pythonでは、ファイナライザは と呼ばれるメソッドです__del__。
Perlでは、ファイナライザは と呼ばれるメソッドですDESTROY。

C# では、ファイナライザ (標準の以前のバージョンでは「デストラクタ」と呼ばれていました) は~、クラス名にプレフィックスが付いた名前のメソッドです。これは C++ のデストラクタ~Fooと同じ構文で、これらのメソッドは、動作は異なるものの、C++ との類推から当初は「デストラクタ」と呼ばれていましたが、混乱を招くため「ファイナライザ」に改名されました。[ 6 ]
C++/CLI では、デストラクタとファイナライザの両方があり、デストラクタはクラス名に~プレフィックスが付いたメソッドで、~Foo(C# のように) のように記述されます。ファイナライザはクラス名に!プレフィックスが付いたメソッドで、 のように記述されます!Foo。
runtime.SetFinalizerGo言語では、ファイナライザは標準ライブラリの関数を呼び出すことで単一のポインタに適用されます。 [ 9 ]
ファイナライザは、オブジェクトがガベージコレクションされるとき、つまりオブジェクトがガベージ(到達不能)になった後、メモリが解放される前に呼び出されます。ファイナライゼーションはガベージコレクタの裁量で非決定的に行われ、決して行われない場合もあります。これは、オブジェクトが使用されなくなるとすぐに決定的に呼び出され、制御不能なプログラム終了の場合を除き常に呼び出されるデストラクタとは対照的です。ファイナライザは、オブジェクト固有の操作を実行する必要があるため、多くの場合インスタンスメソッドです。
ガベージコレクタは、オブジェクトの復活の可能性も考慮する必要があります。最も一般的な方法は、まずファイナライザを実行し、次にオブジェクトが復活したかどうかを確認し、復活した場合はその破棄を中止することです。この追加のチェックはコストがかかる可能性があり、単純な実装では、ファイナライザを持つオブジェクトが1つでもあれば、すべてのガベージを再チェックするため、ガベージコレクションが遅くなり、複雑化します。このため、ファイナライザを持つオブジェクトは、ファイナライザを持たないオブジェクトよりも収集頻度が低く(特定のサイクルでのみ)、即時ファイナライゼーションに依存することによって発生するリソースリークなどの問題を悪化させる可能性があります。
オブジェクトが復活した場合、次に破棄される際にファイナライザが再度呼び出されるかどうかという問題が残ります。デストラクタとは異なり、ファイナライザは複数回呼び出される可能性があります。復活したオブジェクトに対してファイナライザが呼び出されると、オブジェクトが繰り返し復活して破壊不可能になる可能性があります。これは、Python 3.4 より前の CPython 実装や、C# などの CLR 言語で発生します。これを回避するため、Java、Objective-C (少なくとも最近の Apple 実装)、Python 3.4 以降の Python など、多くの言語では、オブジェクトは最大で 1 回しかファイナライズされません。そのため、オブジェクトが既にファイナライズされているかどうかを追跡する必要があります。
C#のようなCLR言語など、他のケースでは、ファイナライゼーションはオブジェクト自体とは別に追跡され、オブジェクトはファイナライゼーションのために繰り返し登録または登録解除される可能性があります。
実装によっては、ファイナライザは多くの問題を引き起こす可能性があり、そのため多くの機関から強く推奨されていません。[ 10 ] [ 11 ]これらの問題には以下が含まれます。[ 10 ]
さらに、プログラミングエラーや予期しない到達可能性によって、オブジェクトがガベージになると想定される時期を過ぎても到達可能な状態が続く場合、ファイナライザの実行に失敗する可能性があります。たとえば、Python が例外をキャッチした場合(または対話モードで例外がキャッチされなかった場合)、例外が発生したスタック フレームへの参照が保持され、そのスタック フレームから参照されるオブジェクトが生存した状態になります。
Javaでは、スーパークラスのファイナライザがサブクラスのガベージコレクションを遅くする可能性もあります。ファイナライザがサブクラスのフィールドを参照する可能性があるため、ファイナライザが実行された後の次のサイクルまでフィールドをガベージコレクションできないためです。[ 10 ]これは、継承よりもコンポジションを使用することで回避できます。
一般的なアンチパターンとして、C++ のリソース取得は初期化(RAII) イディオムにならって、ファイナライザを使用してリソースを解放するという方法があります。つまり、初期化子 (コンストラクタ) でリソースを取得し、ファイナライザ (デストラクタ) でそれを解放するというものです。しかし、これはいくつかの理由でうまくいきません。最も基本的な理由は、ファイナライザは呼び出されない可能性があり、呼び出されたとしても適切なタイミングで呼び出されない可能性があるため、ファイナライザを使用してリソースを解放すると、一般的にリソースリークが発生します。さらに、ファイナライザは規定の順序で呼び出されるわけではありませんが、リソースは多くの場合、特定の順序で解放する必要があり、多くの場合、取得された順序とは逆の順序で解放する必要があります。また、ファイナライザはガベージコレクタの裁量で呼び出されるため、リソースのプレッシャーに関係なく、マネージドメモリのプレッシャーがかかったとき (マネージドメモリがほとんどない場合) にのみ呼び出されることがよくあります。つまり、希少なリソースがガベージによって保持されていても、マネージドメモリが十分に利用可能な場合は、ガベージコレクションは発生せず、これらのリソースが解放されない可能性があります。
したがって、ガベージコレクション言語では、自動的なリソース管理にファイナライザを使用する代わりに、通常はdisposeパターンを使用してリソースを手動で管理する必要があります。この場合、リソースはオブジェクトのインスタンス化時に明示的に呼び出されるイニシャライザで取得されますが、disposeメソッドで解放されます。disposeメソッドは明示的に呼び出される場合もあれば、C#の`with-resources` using、Javaのtry`with-resources`、Pythonの`with-resources`などの言語構造によって暗黙的に呼び出される場合もありますwith。
しかし、場合によっては、リソースの解放にdisposeパターンとfinalizerの両方が使用されることがあります。これは主にC#などのCLR言語で見られ、finalizationはdisposeのバックアップとして使用されます。リソースが取得されると、取得したオブジェクトはfinalizationのためにキューに入れられ、手動でリソースが解放されなくても、オブジェクトが破棄されるときにリソースが解放されます。
オブジェクトのライフタイムが決定論的な言語、特にC++では、リソース管理はリソースの保持期間をオブジェクトのライフタイムに連動させることで行われることが多く、初期化時にリソースを取得し、終了時にリソースを解放します。これはリソース取得と初期化(RAII)として知られています。これにより、リソースの保持がクラス不変となり、オブジェクトが破棄される際にリソースが速やかに解放されることが保証されます。
しかし、C#、Java、Pythonなど、ガベージコレクションを備えた主要な言語すべてを含む、オブジェクトのライフタイムが非決定的な言語では、この方法は機能しません。ファイナライゼーションが適切なタイミングで行われなかったり、まったく行われなかったりする可能性があるため、リソースが長時間解放されないか、まったく解放されない場合があり、リソースリークが発生します。これらの言語では、リソースは一般的にdisposeパターンを使用して手動で管理されます。リソースは初期化中に取得される可能性がありますが、メソッドを呼び出すことで解放されますdispose。とはいえ、これらの言語でリソースを解放するためにファイナライゼーションを使用することは一般的なアンチパターンであり、呼び出しを忘れるとdisposeリソースリークが発生します。
場合によっては、明示的な破棄メソッドを使用するだけでなく、バックアップとしてファイナライゼーション中に保持されているリソースも解放するなど、両方の手法が組み合わされます。これはC#でよく見られる手法で、リソースが取得されるたびにファイナライゼーション対象オブジェクトを登録し、リソースが解放されるたびにファイナライゼーションを抑制することで実現されます。
ユーザー指定のファイナライザが許可されている場合、ファイナライザは任意のコードを実行できるため、ファイナライズ処理によってオブジェクトが復活する可能性があります。これは、ファイナライザが、生きたオブジェクトから破棄されるオブジェクトへの参照を作成する可能性があるためです。ガベージコレクションのない言語では、これは深刻なバグであり、ダングリング参照やメモリ安全性の違反を引き起こします。ガベージコレクションのある言語では、ガベージコレクタによってこの問題は防止されます。最も一般的な方法は、ガベージコレクションに別のステップを追加することです(すべてのユーザー指定のファイナライザを実行した後、復活の有無を確認します)。しかし、このステップを追加すると、ガベージコレクションが複雑になり、処理速度が低下します。
さらに、オブジェクトの復活とは、オブジェクトが破棄されないことを意味し、異常なケースでは、オブジェクトがファイナライズ中に常に自身を復活させ、自身を破壊不可能にしてしまう可能性があります。これを防ぐため、JavaやPython(Python 3.4以降)などの一部の言語では、オブジェクトを一度だけファイナライズし、復活したオブジェクトはファイナライズしません。具体的には、オブジェクトごとにファイナライズされたかどうかを追跡することでこれを実現しています。Objective-Cも(少なくとも最近のAppleバージョンでは)同様の理由でファイナライズを追跡し、復活をバグとして扱います。
.NETフレームワーク、特に C# とVisual Basic (.NET)では、異なるアプローチが採用されています。ここでは、ファイナライゼーションはオブジェクトではなく「キュー」によって追跡されます。この場合、ユーザー指定のファイナライザが提供されていると、デフォルトではオブジェクトは一度だけファイナライゼーションされます (作成時にファイナライゼーションのためにキューに入れられ、ファイナライゼーションが完了するとキューから取り出されます)。ただし、これはモジュールを呼び出すことで変更できます。ファイナライゼーションは、オブジェクトをキューから取り出す をGC呼び出すことで防止でき、オブジェクトをキューに入れる を呼び出すことで再開できます。これらは、ディスポーズ パターンの補足としてリソース管理にファイナライゼーションを使用する場合や、オブジェクト プールを実装する場合に特に使用されます。GC.SuppressFinalizeGC.ReRegisterForFinalize
ファイナライゼーションは形式的には初期化と相補的な関係にあります。初期化はライフサイクルの開始時に行われ、ファイナライゼーションは終了時に行われますが、実際には大きく異なります。変数とオブジェクトはどちらも初期化されますが、これは主に値を割り当てるためです。しかし、一般的にファイナライゼーションされるのはオブジェクトのみであり、通常は値をクリアする必要はありません。メモリはオペレーティングシステムによって解放され、再利用されるからです。
初期化は、初期値を割り当てるだけでなく、主にリソースの取得や、オブジェクトを何らかのサービス(イベントハンドラなど)に登録するために使用されます。これらのアクションには対称的な解放または登録解除アクションがあり、RAII で行われるファイナライザで対称的に処理できます。しかし、多くの言語、特にガベージコレクションを備えた言語では、オブジェクトのライフタイムは非対称です。オブジェクトの作成はコード内の特定の時点で決定論的に行われますが、オブジェクトの破棄はガベージコレクタの裁量で、指定されていない環境で非決定論的に行われます。この非対称性により、ファイナライゼーションは適切なタイミングで、指定された順序で、または指定された環境で行われないため、初期化の補完として効果的に使用することはできません。オブジェクトを特定の時点で破棄することで対称性は部分的に回復されますが、この場合、破棄と破棄は同じ時点で行われず、オブジェクトが「破棄されているがまだ生きている」状態になる可能性があり、クラス不変条件が弱まり、使用が複雑になります。
変数は通常、そのライフサイクルの開始時に初期化されますが、ライフサイクルの終了時にはファイナライズされません。ただし、変数の値がオブジェクトである場合は、そのオブジェクトがファイナライズされることがあります。場合によっては、変数自体もファイナライズされます。GCC拡張機能では、変数のファイナライズが可能です。
finallyその命名からもわかるように、「ファイナライゼーション」とfinally構文はどちらも同様の目的を果たします。つまり、何かの処理が完了した後に、最終的な処理(一般的にはクリーンアップ)を実行するということです。両者の違いは、finally実行されるタイミングにあります。句は、プログラムの実行が関連する句の本体を離れるときtryに実行されます。これはスタックの巻き戻し中に発生し、そのため、順番に保留中の句のスタックが存在します。finally一方、ファイナライゼーションは、メモリ管理方法に応じてオブジェクトが破棄されるときに発生し、一般的には、ファイナライゼーションを待っているオブジェクトのセット(多くの場合ヒープ上)が存在するだけで、特定の順序で実行される必要はありません。
しかし、場合によってはこれらが一致することもあります。C++ では、オブジェクトの破棄は決定論的であり、finally句の動作は、値としてオブジェクトを持つローカル変数を用意し、そのスコープがtry句の本体に対応するブロックである場合に再現できます。実行がこのスコープを抜けると、句が存在する場合とまったく同じように、オブジェクトはファイナライズ(破棄)されますfinally。このため、C++ には構文がありません。finally違いは、ファイナライズが句の呼び出し箇所ではなく、クラス定義のデストラクタメソッドとして定義されている点ですfinally。
逆に、Python ジェネレータなどのコルーチンfinally内の句の場合、コルーチンは決して終了せず、常に処理を中断するだけなので、通常の実行では句は実行されません。コルーチンのインスタンスをオブジェクトとして解釈する場合、句はオブジェクトのファイナライザとみなすことができ、インスタンスがガベージコレクションされるときに実行されます。Python の用語では、コルーチンの定義はジェネレータ関数であり、そのインスタンスはジェネレータイテレータです。したがって、ジェネレータ関数内の句は、この関数からインスタンス化されたジェネレータイテレータのファイナライザになります。finallyfinallyfinally
オブジェクト破棄の独立したステップとしてのファイナライゼーションの概念は、Martin & Odell (1992) のオブジェクト構築における初期化の区別との類推により、Montgomery (1994) に遡ります。[13]この時点以前の文献では、このプロセスに「破棄」という用語が使用され、ファイナライゼーションと解放が区別されていませんでした。また、C++ や Perl など、この時期に登場したプログラミング言語では、「破棄」という用語が使用されています。「finalize」と「finalization」という用語は、影響力のある書籍Design Patterns (1994) でも使用されています。[ a ] [ 15 ] 1995 年に Java が導入されたことで、この用語が普及し、ガベージ コレクションと関連付けられるメソッドが含まれ、それ以降の言語では一般的にこの区別がなされ、特にガベージ コレクションの文脈で「ファイナライゼーション」という用語が使用されています。finalize
Dispose.Dispose.__del__()メソッドは、外部不変条件を維持するために必要な最小限のことだけを行うべきである。」