web-dev-qa-db-ja.com

なぜfinalize()を実装するのでしょうか?

finalize()に関する新人Javaの多くの質問を読んでおり、finalize()がリソースをクリーンアップするための信頼性の低い方法であることを誰も明らかにしていないのはちょっと戸惑っています。 Connectionsをクリーンアップするためにそれを使用するというコメントを誰かが見ました。Connectionが閉じられているという保証に近づくには、try(catch)を最後に実装するしかありません。

私はCSで教育を受けていませんでしたが、10年近くJavaでプロとしてプログラミングをしており、実稼働システムでfinalize()を実装する人を見たことはありません。これはまだ、その用途がないことや、私が一緒に仕事をしたことがある人がそれを正しく行っていることを意味するものではありません。

だから私の質問は、言語内の別のプロセスまたは構文を介してより確実に処理できないfinalize()を実装するためのユースケースは何ですか?

特定のシナリオまたはあなたの経験を提供してください、単にJavaテキスト本を繰り返すか、この質問の意図ではないので、ファイナライズの意図された使用は十分ではありません。

355
Spencer Kormos

外部リソース(ソケット、ファイルなど)を保持するオブジェクトのバックストップとして使用できます。 close()メソッドを実装し、呼び出す必要があることを文書化します。

finalize()を実装して、close()処理が実行されていないことが検出された場合は、それを実行します。たぶん、stderrに何かがダンプされて、バグのある呼び出し元の後にクリーンアップしていることを指摘します。

例外的/バギーな状況で追加の安全性を提供します。すべての呼び出し元が毎回正しいtry {} finally {}を行うわけではありません。残念ながら、ほとんどの環境で真実です。

ほとんど必要ないことに同意します。また、コメンターが指摘しているように、GCのオーバーヘッドが伴います。長時間実行するアプリで「ベルトとサスペンダー」の安全性が必要な場合にのみ使用してください。

Java 9の時点で、 Object.finalize() は非推奨です!代替として Java.lang.ref.Cleaner および Java.lang.ref.PhantomReference を示しています。

216
John M

finalize()は、不特定の時間にコードを実行するのが良いかもしれないというJVMへのヒントです。これは、コードを不思議なほど実行に失敗させたい場合に適しています。

ファイナライザで重要なこと(基本的にロギング以外のこと)は、次の3つの状況でも有効です。

  • ファイナライズされた他のオブジェクトは、プログラムの残りの部分が有効とみなす状態のままであることをギャンブルしたい場合。
  • ファイナライザを持つすべてのクラスのすべてのメソッドに多くのチェックコードを追加して、ファイナライズ後にそれらが正しく動作することを確認します。
  • ファイナライズされたオブジェクトを誤って復活させ、それらが機能しない理由、および/または最終的にリリースされたときにファイナライズされない理由を見つけるために多くの時間を費やしたい場合。

Finalize()が必要だと思う場合、時々本当に欲しいのはファントム参照です(これは与えられた例ではその参照によって使用される接続へのハード参照を保持し、ファントムの後に閉じます参照はキューに入れられました)。これには、不思議なことに決して実行されないという特性もありますが、少なくとも、ファイナライズされたオブジェクトのメソッドを呼び出したり、復活させることはできません。そのため、その接続を完全に閉じる必要は絶対にないが、あなたは非常に望んでおり、クラスのクライアントは自分自身を呼び出すことができない、または呼び出さないという状況にはちょうどいいです(実際には十分です- require収集の前に特定のアクションを実行するインターフェイスを設計する場合、ガベージコレクターを使用することのポイントは何ですか? malloc/freeの日数。)

他の場合は、管理していると思うリソースをより堅牢にする必要があります。たとえば、その接続を閉じる必要があるのはなぜですか?最終的には、システムによって提供される何らかの種類のI/O(ソケット、ファイルなど)に基づいている必要があるため、リソースの最低レベルがgcedになったときにシステムに依存して閉じられないのはなぜですか?反対側のサーバーが、単にソケットを落とすのではなく、接続をきれいに閉じることを絶対に要求する場合、コードが実行されているマシンの電源ケーブルを誰かがつまずいたとき、または介在するネットワークがなくなったときにどうなりますか?

免責事項:私は過去にJVMの実装に取り​​組んできました。ファイナライザーが嫌いです。

163
Steve Jessop

単純なルール:ファイナライザーを使用しないでください。

オブジェクトがファイナライザを持っているという事実だけで(オブジェクトが実行するコードに関係なく)、ガベージコレクションにかなりのオーバーヘッドを引き起こすのに十分です。

ブライアン・ゲッツの 記事 から:

ファイナライザを備えたオブジェクト(非自明なfinalize()メソッドを備えたオブジェクト)は、ファイナライザを備えていないオブジェクトと比較してかなりのオーバーヘッドがあるため、控えめに使用する必要があります。ファイナライズ可能なオブジェクトは、割り当てが遅く、収集が遅くなります。割り当て時に、JVMはファイナライズ可能なオブジェクトをガベージコレクターに登録する必要があり、(少なくともHotSpot JVM実装では)ファイナライズ可能なオブジェクトは他のほとんどのオブジェクトよりも遅い割り当てパスに従う必要があります。同様に、ファイナライズ可能なオブジェクトも収集に時間がかかります。ファイナライズ可能なオブジェクトを再利用できるようになるには、少なくとも2つのガベージコレクションサイクル(最良の場合)が必要であり、ガベージコレクタはファイナライザを呼び出すために余分な作業を行う必要があります。その結果、到達不可能なファイナライズ可能なオブジェクトによって使用されるメモリがより長く保持されるため、オブジェクトの割り当てと収集に費やされる時間が長くなり、ガベージコレクターに対する負荷が大きくなります。それと、ファイナライザが予測可能な時間枠で実行されることを保証されていない、またはまったく実行されていないという事実と組み合わせると、ファイナライズが適切なツールである状況が比較的少ないことがわかります。

53
Tom

実稼働コードでファイナライズを使用したのは、特定のオブジェクトのリソースがクリーンアップされたことを確認することだけでした。それは実際にそれを試そうとはしませんでした、それがきちんと行われなかったなら、それはただたくさん叫びました。非常に有用であることが判明しました。

43
skaffman

1998年以来、専門的にJavaをやっていますが、finalize()を実装したことはありません。一度も。

33
Paul Tomblin

これで何ができるかわかりませんが...

itsadok@laptop ~/jdk1.6.0_02/src/
$ find . -name "*.Java" | xargs grep "void finalize()" | wc -l
41

ですから、Sunはsomeを使用する必要がある(と考える)ケースを見つけたと思います。

26
itsadok

受け入れられた答えは良いです、私はちょうど実際にそれをまったく使用せずにファイナライズの機能を持つ方法があることを付け加えたかっただけです。

「参照」クラスを見てください。弱い参照、ファントム参照とソフト参照。

それらを使用してすべてのオブジェクトへの参照を保持できますが、この参照はGCを停止しません。これについての素晴らしいことは、それが削除されるときにメソッドを呼び出すことができ、このメソッドはguaranteedを呼び出すことができるということです。

ファイナライズに関しては、どのオブジェクトが解放されているかを理解するために、一度ファイナライズを使用しました。静的、参照カウントなどのいくつかのきちんとしたゲームをプレイできますが、分析のためだけでしたが、このようなコードには注意してください(ファイナライズだけでなく、それが最もよく見られる場所です):

public void finalize() {
  ref1 = null;
  ref2 = null;
  othercrap = null;
}

それは誰かが彼らが何をしていたのか知​​らなかったというサインです。このような「クリーンアップ」はほとんど必要ありません。クラスがGCされると、これは自動的に行われます。

ファイナライズでそのようなコードを見つけた場合、それを書いた人が混乱したことが保証されます。

それが他の場所にある場合、コードは悪いモデルに対する有効なパッチである可能性があります(クラスは長期間存在し続け、何らかの理由でオブジェクトがGCされる前に手動で解放する必要がありました)。一般的に、誰かがリスナーや何かを削除するのを忘れて、オブジェクトがGCされていない理由がわからないので、それが参照するものを削除し、肩をすくめて離れます。

「Quicker」のクリーンアップには決して使用しないでください。

26
Bill K
class MyObject {
    Test main;

    public MyObject(Test t) {    
        main = t; 
    }

    protected void finalize() {
        main.ref = this; // let instance become reachable again
        System.out.println("This is finalize"); //test finalize run only once
    }
}

class Test {
    MyObject ref;

    public static void main(String[] args) {
        Test test = new Test();
        test.ref = new MyObject(test);
        test.ref = null; //MyObject become unreachable,finalize will be invoked
        System.gc(); 
        if (test.ref != null) System.out.println("MyObject still alive!");  
    }
}

====================================

結果:

This is finalize

MyObject still alive!

=====================================

したがって、finalizeメソッドで到達不能なインスタンスを到達可能にすることができます。

21
Kiki

finalize()は、リソースリークをキャッチするのに役立ちます。リソースを閉じる必要があるが、閉じられていないという事実をログファイルに書き込んで閉じない場合。そうすることで、リソースリークを削除し、それが発生したことを確認できるようにして、修正できるようにします。

私は1.0 alpha 3(1995)以来Javaでプログラミングを行ってきましたが、まだファイナライズをオーバーライドしていません...

9
TofuBeer

リソースをクリーンアップするためにfinalize()に依存しないでください。その場合、クラスがガベージコレクションされるまでfinalize()は実行されません。リソースを使い終わったら、明示的にリソースを解放する方がはるかに優れています。

6
Bill the Lizard

上記の回答のポイントを強調するには、ファイナライザーは、単一のGCスレッドで実行されます。開発者がいくつかのファイナライザに小さなスリープを追加し、意図的にそれ以外の派手な3Dデモをひざまずかせた、Sunの主要なデモを聞いたことがあります。

Test-env診断の例外を除き、避けるのが最善です。

JavaのEckelの思考には 良いセクション があります。

5
Michael Easter

finalize()で行うことに注意してください。特に、close()を呼び出してリソースをクリーンアップするなどの目的で使用する場合。実行中のJavaコードにJNIライブラリがリンクされているいくつかの状況に遭遇し、finalize()を使用してJNIメソッドを呼び出す状況では、非常に悪いJavaになります_ヒープ破損。基礎となるJNIコード自体が破損の原因ではなく、ネイティブライブラリのメモリトレースはすべて正常でした。それは、finalize()からJNIメソッドを呼び出していたという事実だけでした。

これは、まだ広く使用されているJDK 1.5で行われました。

後で問題が発生することはわかりませんが、結局、犯人は常にJNI呼び出しを使用するfinalize()メソッドでした。

3

うーん、私はかつてそれを使用して、既存のプールに戻されなかったオブジェクトをクリーンアップしました。

彼らはたくさん回ったので、いつ安全にプールに戻ることができるかを知ることは不可能でした。問題は、ガベージコレクション中に、オブジェクトのプールによる節約よりもはるかに大きなペナルティが発生することでした。プール全体をリッピングし、すべてをダイナミックにし、それで完成するまで、約1か月間本番環境でした。

3
patros

リソースを解放するために何らかの「クリーンアップ」メソッドを呼び出す必要がある他の開発者が使用するコードを記述するとき。他の開発者は、クリーンアップ(またはクローズ、破棄など)メソッドの呼び出しを忘れることがあります。リソースリークの可能性を回避するには、finalizeメソッドをチェックインして、メソッドが呼び出されたことを確認し、そうでない場合は自分で呼び出すことができます。

多くのデータベースドライバーは、ステートメントと接続の実装でこれを行い、それらの近くで呼び出すことを忘れた開発者に対して少しの安全性を提供します。

3
John Meagher

編集:さて、それは本当に機能しません。私はそれを実装し、それが時々失敗したらそれでいいと思ったが、ファイナライズメソッドを一度も呼び出さなかった。

私はプロのプログラマーではありませんが、私のプログラムでは、finalize()を使用する良い例の例だと思うケースがあります。これは、コンテンツを破棄する前にディスクに書き込むキャッシュです。破壊するたびに実行する必要はないので、プログラムを高速化するだけです。間違っていないことを願っています。

@Override
public void finalize()
{
    try {saveCache();} catch (Exception e)  {e.printStackTrace();}
}

public void saveCache() throws FileNotFoundException, IOException
{
    ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("temp/cache.tmp"));
    out.writeObject(cache);
}
2
user265243

グローバル/静的な場所に追加された(不要な)ものを削除すると便利です。また、オブジェクトを削除するときに削除する必要があります。例えば:

 private void addGlobalClickListener(){
 weakAwtEventListener = new WeakAWTEventListener(this); 
 
 Toolkit.getDefaultToolkit()。addAWTEventListener(weakAwtEventListener、AWTEvent.MOUSE_EVENT_MASK); 
} 
 
 @Override 
 protected void finalize()throws Throwable {
 super.finalize(); 
 
 if(weakAwtEventListener!= null){
 Toolkit.getDefaultToolkit()。removeAWTEventListener(weakAwtEventListener); 
} 
} 
2

サイドノートとして:

Finalize()をオーバーライドするオブジェクトは、ガベージコレクターによって特別に処理されます。通常、オブジェクトはスコープから外れた後、コレクションサイクル中にすぐに破棄されます。ただし、ファイナライズ可能なオブジェクトは代わりにキューに移動され、個別のファイナライズスレッドがキューを排出し、各オブジェクトでfinalize()メソッドを実行します。 finalize()メソッドが終了すると、オブジェクトは次のサイクルでガベージコレクションの準備が整います。

ソース: finalize()Java-9で非推奨

0
Sudip Bhandari

iirc-高価なリソースのプーリングメカニズムを実装する手段としてfinalizeメソッドを使用できます-そのため、GCも取得しません。

0
JGFMK

個人的に、まれな状況を除いて、finalize()をほとんど使用しませんでした。カスタムジェネリック型コレクションを作成し、次のことを行うカスタムfinalize()メソッドを作成しました。

public void finalize() throws Throwable {
    super.finalize();
    if (destructiveFinalize) {
        T item;
        for (int i = 0, l = length(); i < l; i++) {
            item = get(i);
            if (item == null) {
                continue;
            }
            if (item instanceof Window) {
                ((Window) get(i)).dispose();
            }
            if (item instanceof CompleteObject) {
                ((CompleteObject) get(i)).finalize();
            }
            set(i, null);
        }
    }
}

CompleteObjectは、#finalize()#hashCode()#clone()などのめったに実装されないObjectメソッドを実装したことを指定できるように作成したインターフェイスです)

ですから、姉妹の#setDestructivelyFinalizes(boolean)メソッドを使用して、私のコレクションを使用するプログラムは、このコレクションへの参照を破棄すると、そのコンテンツへの参照も破棄し、JVMを意図せずに維持する可能性のあるウィンドウを破棄することを保証できます。スレッドを停止することも検討しましたが、それによってまったく新しいワームの缶が開かれました。

0
Supuhstar

リソース(ファイル、ソケット、ストリームなど)は、終了したら閉じる必要があります。通常、これらにはtry-catchステートメントのfinallyセクションで呼び出すclose()メソッドがあります。 finalize()は少数の開発者でも使用できる場合がありますが、ファイナライズが常に呼び出されるという保証がないため、IMOは適切な方法ではありません。

Java 7には、次のように使用できる try-with-resources ステートメントがあります。

try (BufferedReader br = new BufferedReader(new FileReader(path))) {
  // Processing and other logic here.
} catch (Exception e) {
  // log exception
} finally {
  // Just in case we need to do some stuff here.
}

上記の例では、try-with-resourceはclose()メソッドを呼び出して、リソースBufferedReaderを自動的に閉じます。必要に応じて、独自のクラスで Closeable を実装し、同様の方法で使用することもできます。 IMOの方がわかりやすく、理解しやすいようです。

0
i_am_zero

承認済みの回答には、ファイナライズ中にリソースを閉じることができると記載されています。

ただし、 この回答 は、少なくともJITコンパイラーを備えたJava8では、オブジェクトによって維持されているストリームからの読み取りを完了する前であっても、ファイナライザーが呼び出されるという予期しない問題が発生することを示しています。

したがって、そのような状況でも、finalizeの呼び出しはnotが推奨されます。

0
Joeblade