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


371

私は多くの新人のJavaの質問を読んでいて、finalize()finalize()がリソースをクリーンアップするための信頼できない方法であることを誰も実際に明らかにしていないことは、ちょっと困惑していることがわかりました。Connectionsをクリーンアップするためにそれを使用するという誰かのコメントを見ました。Connectionがクローズされているという保証に近づく唯一の方法は、最終的にtry(catch)を実装することなので、これは本当に怖いです。

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

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

この質問の意図ではないため、特定のシナリオまたは経験を提供してください。単にJavaの教科書を繰り返すか、ファイナライズの使用目的だけでは十分ではありません。


HI非常によく、ここで説明するのfinalize()メソッド howtodoinjava.com/2012/10/31/...
サミールカジ

2
ほとんどのアプリケーションとライブラリのコードは決して使用しませんfinalize()。ただし、呼び出し元に代わってネイティブリソースを管理するなどのプラットフォームライブラリコードはSocketInputStreamリソースリークのリスクを最小限に抑えるようにしています(またはPhantomReference、後で追加されたのような同等のメカニズムを使用しています)。 、開発者の99.9999%が作成することはありません。
ブライアンゲッツ

回答:


231

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

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

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

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

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


44
finalize()によって例外がスローされないことを確認してください。例外が発生すると、ガベージコレクタがそのオブジェクトのクリーンアップを続行せず、メモリリークが発生します。
skaffman 2008年

17
Finalizeの呼び出しは保証されていないため、リソースの解放を期待しないでください。
2008年

19
skaffman-信じられません(バグのあるJVM実装は別として)。Object.finalize()javadocから:キャッチされない例外がfinalizeメソッドによってスローされた場合、例外は無視され、そのオブジェクトのファイナライズは終了します。
John M

4
あなたの提案は長い間人々のバグ(近づいていない)を隠すことができます。そうすることはお勧めしません。
kohlerm 2008年

64
私は実際にこの正確な理由でファイナライズを使用しました。私はコードを継承しましたが、非常にバグが多く、データベース接続を開いたままにする傾向がありました。接続が作成された日時と場所に関するデータを含むように接続を変更し、接続が適切に閉じられなかった場合にこの情報をログに記録するようにファイナライズを実装しました。閉じられていない接続を追跡するのに非常に役立ちます。
brainimus

174

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

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

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

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

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

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


76
極度の皮肉正義に賛成。
認識

32
これは質問の答えにはなりません。それは、finalize()誰かが本当にそれを使いたいと思う理由を与えずに、すべての欠点を述べているだけです。
かたつむり2012年

1
@supercat:ファントム参照(すべての参照について)がJava 2で導入されました
Steve Jessop

1
@supercat:申し訳ありませんが、「参照」とはjava.lang.ref.Reference、私が意図したものとそのサブクラスです。Java 1には変数がありました:-)そして、はい、ファイナライザもありました。
スティーブジェソップ2013年

2
@SteveJessop:基本クラスコンストラクターが他のエンティティに、構築中のオブジェクト(非常に一般的なパターン)に代わってそれらの状態を変更するように要求した場合、派生クラスフィールド初期化子が例外をスローすると、部分的に構築されたオブジェクトは直ちにクリーンアップする必要がありますそれ自体(つまり、それらの外部エンティティにサービスが不要になったことを通知する)ですが、Javaも.NETも、それが発生する可能性のある、リモートでクリーンなパターンを提供していません。実際、彼らは、クリーンアップが必要なものへの参照がクリーンアップコードに到達するのを困難にするために、彼らの邪魔をするように見えます。
スーパーキャット2013年

58

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

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

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

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


46

本番コードでfinalizeを使用したのは、特定のオブジェクトのリソースがクリーンアップされているかどうかのチェックを実装するときだけでした。クリーンアップされていない場合は、非常に音声メッセージをログに記録しました。それは実際にそれを試みてそれをしなかった、それが正しく行われなかった場合それは多くのことを叫んだ。かなり便利であることがわかりました。


35

私は1998年からJavaを専門的にやっており、実装したことはありませんfinalize()。一度はありません。


次に、パブリッククラスオブジェクトをどのように破棄しますか?ADFで問題が発生しています。基本的には、ページをリロード(APIから新しいデータを取得)することを想定していますが、古いオブジェクトを
取得し

2
@ InfantPro'Aravind 'でfinalizeを呼び出しても、オブジェクトは削除されません。これは、JVMがオブジェクトをガベージコレクションする場合に、JVMが呼び出すことを選択できる実装方法です。
Paul Tomblin 2016年

@PaulTomblin、その詳細をありがとう。ADFのページを更新する提案はありますか?
InfantPro'Aravind '

@ InfantPro'Aravind 'わからない、ADFを使用したことがない。
Paul Tomblin

28

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

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

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

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

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

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

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

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

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


3
ファントム参照は、解放されているオブジェクトを追跡するためのより良い方法だと思います。
Bill Michell、

2
私が聞いたことがある「弱い参照」の最も良い説明を提供するための賛成票。
sscarduzio 2013年

これを読むのに何年もかかったことを残念に思いますが、それは答えに本当に良い追加です。
Spencer Kormos 2014

27

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

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

だから私は太陽がそれを使うべきだと彼らが思ういくつかのケースを見つけたと思います。


12
「Sunの開発者は常に正しいのでしょうか」という問題でもあると思います。
CodeReaper 2012年

13
しかしfinalize、実際にそれを使用するのではなく、それらの用途のいくつがサポートを実装またはテストしているだけですか?
かたつむり2012年

私はWindowsを使用しています。Windowsでどのように同じことをしますか?
gparyani 2013

2
@damryfbfnetsi:ACK(インストールしてくださいbeyondgrep.comを介して)chocolatey.org/packages/ack。または、の単純なファイル内検索機能を使用し$FAVORITE_IDEます。
ブライアンクライン2014年

21
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
どういうわけか、このコードは私にゾンビ映画を思い起こさせます。オブジェクトをGCすると、再び立ち上がる...
2012年

上記は良いコードです。ちょうど今、オブジェクトを再び到達可能にする方法を考えていました。
lwpro2 2013年

15
いいえ、上記は悪いコードです。ファイナライズが悪い理由の例です。
Tony

呼び出しSystem.gc();は、ガベージコレクタが実際に実行されることを保証するものではないことに注意してください。ヒープをクリーンアップするのはGCの完全な裁量です(おそらく、まだ十分な空きヒープがあり、断片化されていない可能性があります...)。だから、それはプログラマーからの謙虚な要求にすぎません。「私が言うように今すぐ実行してください!」というコマンドではありません。言葉を使って申し訳ありませんが、JVMのGCがどのように機能するかを正確に示しています。
ローランド

10

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

私は1.0 alpha 3(1995)以来Javaでプログラミングしており、finalizeをオーバーライドしてまだ何もしていません...


6

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


5

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

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

Eckel's Thinking in Javaにはこれに関する良いセクションがあります。


4

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

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


4

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

これは、まだ広く使用されているJDK 1.5に関するものです。

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


3

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

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


2

編集:さて、それは本当に機能しません。私はそれを実装し、失敗したとしてもそれでいいと考えましたが、finalizeメソッドを1回も呼び出しませんでした。

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

@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);
}

(ディスクにフラッシュするタイプの)キャッシュが良い例だと思います。そして、ファイナライズが呼び出されなくても、汗はありません。
foo

2

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

    プライベートvoid addGlobalClickListener(){
        weakAwtEventListener = new WeakAWTEventListener(this);

        Toolkit.getDefaultToolkit()。addAWTEventListener(weakAwtEventListener、AWTEvent.MOUSE_EVENT_MASK);
    }

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

        if(weakAwtEventListener!= null){
            Toolkit.getDefaultToolkit()。removeAWTEventListener(weakAwtEventListener);
        }
    }

これは、リスナーがthisコンストラクターで渡されたインスタンスへの強い参照を持っている場合、そのインスタンスが到達不能にfinalize()なることはないため、追加の作業が必要です。一方、その名前が示すように、リスナーがそのオブジェクトへの弱い参照を保持している場合、この構成体はクリーンアップが不要なときにクリーンアップされるリスクがあります。オブジェクトのライフサイクルは、アプリケーションのセマンティクスを実装するための適切なツールではありません。
Holger

0

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


0

補足として:

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

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


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

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


0

個人的には、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あなたはめったに実装されていない実装しましたことを指定することができます私が作ったインターフェースであるObjectようなメソッドを#finalize()#hashCode()#clone()

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


4
呼び出しfinalize()あなたにCompleteObjectJVMがまだ起動可能性があるとして、あなたは上記のコードで行うようにインスタンスすることは、それが二回呼び出される可能性があることを意味しfinalize()、これらのオブジェクトは、実際に到達不能になった後は、あるかもしれないファイナライズのロジックによるこれ、前にfinalize()あなたの特別なコレクションの方法同時に、または同時に呼び出されます。あなたのメソッドでスレッドの安全性を確保するための対策は見当たらないので、あなたはこれに気づいていないようです…
Holger

0

受け入れられた回答は、ファイナライズ中にリソースを閉じることができることを示しています。

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

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


ファイナライザは任意のスレッドによって呼び出される可能性があるため、操作をスレッドセーフにする必要があります。正しく実装されたスレッドセーフティは、閉じる操作だけでなく、リソースを使用する操作も同じオブジェクトまたはロックで同期する必要があることを意味するため、使用と閉じる操作の間に前に発生する関係があり、これも早すぎるのを防ぎますファイナライズ。しかし、もちろん、使用全体に対して高額です...
Holger
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.