389

私は多くの新人 Java の質問を読んでいますがfinalize()、finalize() がリソースをクリーンアップするための信頼できない方法であることを誰も実際に明らかにしていないことに、ある種の戸惑いを感じています。Connection をクリーンアップするために使用しているというコメントを誰かが見ましたが、これは本当に恐ろしいことです。なぜなら、Connection が閉じられていることを保証する唯一の方法は、try (catch) を最後に実装することだからです。

私は CS の教育を受けていませんでしたが、専門的に Java でプログラミングをして 10 年近くにfinalize()なります。これはまだ、それが役に立たないという意味ではありません。

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

この質問の意図ではないため、特定のシナリオまたは経験を提供してください.

4

21 に答える 21

242

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

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

例外的/バグの多い状況での追加の安全性を提供します。すべての発信者が毎回正しいことを行うわけではありませんtry {} finally {}。残念ながら、ほとんどの環境に当てはまります。

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

Object.finalize()Java 9 の時点で非推奨になっていることがわかりました。彼らは私たちに代替案を指摘してjava.lang.ref.Cleanerjava.lang.ref.PhantomReferenceます。

于 2008-10-01T15:27:50.363 に答える
183

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

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

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

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

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

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

于 2008-10-01T16:05:23.067 に答える
56

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

オブジェクトにファイナライザーがあるという事実だけで (実行するコードに関係なく)、ガベージ コレクションにかなりのオーバーヘッドが発生します。

Brian Goetzの記事から:

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

于 2008-10-01T17:38:32.093 に答える
50

プロダクション コードでファイナライズを使用したのは、特定のオブジェクトのリソースがクリーンアップされているかどうかのチェックを実装するときだけでした。実際に自分でやろうとしたわけではなく、適切に行わないと大声で叫ぶだけでした。非常に便利であることが判明しました。

于 2008-10-01T15:44:18.797 に答える
38

私は 1998 年からプロとして Java を使用していますが、.NET を実装したことはありませんfinalize()。一度もありません。

于 2008-10-01T15:24:23.553 に答える
30

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

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

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

ファイナライズについて: どのオブジェクトが解放されているかを理解するために、ファイナライズを 1 回使用しました。統計や参照カウントなどを使っていくつかの巧妙なゲームをプレイできますが、これは分析のためだけのものでしたが、次のようなコードに注意してください (ファイナライズだけでなく、最もよく見られる場所です)。

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

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

ファイナライズでそのようなコードを見つけた場合、それを書いた人が混乱していることは間違いありません。

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

「より速く」物事をクリーンアップするために使用しないでください。

于 2008-10-01T16:56:03.303 に答える
28

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

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

ですから、Sun は、それを使用すべき (と彼らが考える)いくつかのケースを見つけたと思います。

于 2008-10-05T14:42:56.983 に答える
20
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メソッドで到達不能なインスタンスを到達可能にすることができます。

于 2009-06-02T01:55:24.967 に答える
11

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

私は 1.0 alpha 3 (1995) から Java でプログラミングを行っていますが、ファイナライズをオーバーライドしたことはまだありません...

于 2009-02-13T18:09:38.163 に答える
6

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

于 2008-10-01T15:27:49.520 に答える
5

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

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

何かがうまくいかなかったことに気付くのはずっと後のことでしたが、最終的には常に JNI 呼び出しを使用する finalize() メソッドが原因でした。

于 2008-10-01T16:00:22.017 に答える
5

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

test-env 診断を除いて、避けるのが最善です。

Eckel の Thinking in Java には、これに関する適切なセクションがあります。

于 2008-10-01T17:43:12.083 に答える
4

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

彼らはたくさん回されたので、安全にプールに戻せるかどうかを判断することは不可能でした. 問題は、ガベージ コレクション中に、オブジェクトをプールすることによる節約よりもはるかに大きなペナルティが発生することでした。プール全体を取り出し、すべてを動的にして完成させるまで、約 1 か月間本番環境にありました。

于 2008-10-01T16:15:39.313 に答える
3

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

多くのデータベース ドライバーは、Statement と Connection の実装でこれを行っており、close を呼び出すのを忘れた開発者に対して少し安全を提供します。

于 2008-10-01T15:25:07.407 に答える
2

編集:わかりました、それは本当にうまくいきません。私はそれを実装し、時々失敗しても大丈夫だと思いましたが、ファイナライズメソッドを一度も呼び出しませんでした。

私はプロのプログラマーではありませんが、私のプログラムでは、内容が破棄される前にディスクに書き込むキャッシュである 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);
}
于 2010-02-03T12:10:59.377 に答える
2

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

    プライベートボイド addGlobalClickListener() {
        weakAwtEventListener = 新しい WeakAWTEventListener(this);

        Toolkit.getDefaultToolkit().addAWTEventListener(weakAwtEventListener, AWTEvent.MOUSE_EVENT_MASK);
    }

    @オーバーライド
    protected void finalize() throws Throwable {
        super.finalize();

        if(weakAwtEventListener != null) {
            Toolkit.getDefaultToolkit().removeAWTEventListener(weakAwtEventListener);
        }
    }
于 2011-04-07T08:11:40.953 に答える
1

承認された回答リストには、ファイナライズ中にリソースを閉じることができるというものがあります。

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

したがって、そのような状況でも finalize を呼び出すことはお勧めしません。

于 2014-12-03T12:56:26.890 に答える
0

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

于 2013-09-19T17:01:58.387 に答える
0

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

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.
}

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

于 2015-06-24T03:54:26.150 に答える