Javaのfinalizeメソッドの手引き

1概要

このチュートリアルでは、Java言語の中心的な側面、つまりルートの Object クラスによって提供される finalize メソッドに焦点を当てます。

簡単に言うと、これは特定のオブジェクトのガベージコレクションの前に呼び出されます。

2ファイナライザの使用

finalize() メソッドはファイナライザと呼ばれます。

この特定のインスタンスをガベージコレクションする必要があるとJVMが判断したときに、ファイナライザが呼び出されます。そのようなファイナライザは、オブジェクトを元の状態に戻すことを含む、あらゆる操作を実行できます。

ただし、ファイナライザの主な目的は、オブジェクトがメモリから削除される前に、そのオブジェクトによって使用されていたリソースを解放することです。ファイナライザは、クリーンアップ操作の主要なメカニズムとして、または他の方法が失敗したときのセーフティネットとして機能します。

ファイナライザの動作を理解するために、クラス宣言を見てみましょう。

public class Finalizable {
    private BufferedReader reader;

    public Finalizable() {
        InputStream input = this.getClass()
          .getClassLoader()
          .getResourceAsStream("file.txt");
        this.reader = new BufferedReader(new InputStreamReader(input));
    }

    public String readFirstLine() throws IOException {
        String firstLine = reader.readLine();
        return firstLine;
    }

   //other class members
}

クラス Finalizable には、クローズ可能なリソースを参照するフィールド reader があります。このクラスからオブジェクトが作成されると、クラスパス内のファイルから新しい BufferedReader インスタンス読み取りを構築します。

そのようなインスタンスは、指定されたファイルの最初の行を抽出するために readFirstLine メソッドで使用されます。与えられたコードで読者が閉じられていないことに注意してください。

ファイナライザを使用してそれを実行できます。

@Override
public void finalize() {
    try {
        reader.close();
        System.out.println("Closed BufferedReader in the finalizer");
    } catch (IOException e) {
       //...
    }
}

ファイナライザが通常のインスタンスメソッドとまったく同じように宣言されていることは簡単にわかります。

実際には、** ガベージコレクタがファイナライザを呼び出すタイミングは、JVMの実装とシステムの状態によって異なります。

その場でガベージコレクションを実行するために、 System.gc メソッドを利用します。実社会のシステムでは、いくつかの理由で、これを明示的に呼び出すべきではありません。

  1. 費用がかかる

  2. ただちにガベージコレクションを起動するわけではありません.

JVMがGCを起動するためのヒント 。 GCはGCを呼び出す必要があるときJVMはよく知っています

GCを強制する必要がある場合は、そのために jconsole を使用できます。

以下は、ファイナライザの動作を説明するテストケースです。

@Test
public void whenGC__thenFinalizerExecuted() throws IOException {
    String firstLine = new Finalizable().readFirstLine();
    assertEquals("baeldung.com", firstLine);
    System.gc();
}

最初のステートメントでは、 Finalizable オブジェクトが作成され、その readFirstLine メソッドが呼び出されます。このオブジェクトはどの変数にも割り当てられていないため、 System.gc メソッドが呼び出されたときにガベージコレクションに適しています。

テストのアサーションは入力ファイルの内容を検証し、私たちのカスタムクラスが期待通りに動作することを証明するためだけに使用されます。

提供されたテストを実行すると、ファイナライザでバッファされたリーダーが閉じられているというメッセージがコンソールに表示されます。これは、 finalize メソッドが呼び出され、リソースがクリーンアップされたことを意味します。

これまでのところ、ファイナライザは破棄前の操作に最適な方法です。しかし、そうではありません。

次のセクションでは、それらを使用しないようにする理由を説明します。

3ファイナライザの回避

ファイナライザを使用して重要なアクションを実行するときに直面するいくつかの問題を見てみましょう。

ファイナライザに関連する最初の顕著な問題は、迅速さの欠如です。ガベージコレクションはいつでも発生する可能性があるため、ファイナライザがいつ実行されるかを知ることはできません。

それ自体では、これは問題ではありません。最も重要なことは、遅かれ早かれファイナライザーがまだ呼び出されることです。ただし、システムリソースは限られています。そのため、それらがクリーンアップされる機会を得る前にこれらのリソースを使い果たし、システムクラッシュを引き起こす可能性があります。

ファイナライザもプログラムの移植性に影響を与えます。ガベージコレクションのアルゴリズムはJVMの実装に依存するため、プログラムはあるシステムでは非常にうまく動作しますが、実行時には異なる動作をします。

ファイナライザに伴うもう1つの重要な問題はパフォーマンスコストです。具体的には、 空でないファイナライザ を含むオブジェクトを構築および破棄する場合、** JVMはもっと多くの操作を実行する必要があります。

詳細は実装固有ですが、一般的な考え方はすべてのJVMで同じです。オブジェクトが破棄される前にファイナライザが確実に実行されるようにするには、追加の手順を実行する必要があります。これらのステップは、オブジェクトの作成と破棄の期間を数百倍、さらには数千倍にも増やすことができます。

私たちが話している最後の問題は、ファイナライズ中の例外処理の欠如です。 ** ファイナライザが例外をスローした場合、ファイナライズ処理はキャンセルされ、例外は無視され、オブジェクトは通知なしで破損状態になります。

4ファイナライザなしの例

finalize() メソッドを使用せずに、同じ機能を提供するソリューションを調べてみましょう。以下の例がファイナライザを置き換える唯一の方法ではないことに注意してください。

代わりに、重要な点を説明するために使用されてきました。ファイナライザを回避するのに役立つオプションは常にあります。

これが新しいクラスの宣言です。

public class CloseableResource implements AutoCloseable {
    private BufferedReader reader;

    public CloseableResource() {
        InputStream input = this.getClass()
          .getClassLoader()
          .getResourceAsStream("file.txt");
        reader = new BufferedReader(new InputStreamReader(input));
    }

    public String readFirstLine() throws IOException {
        String firstLine = reader.readLine();
        return firstLine;
    }

    @Override
    public void close() {
        try {
            reader.close();
            System.out.println("Closed BufferedReader in the close method");
        } catch (IOException e) {
           //handle exception
        }
    }
}

新しい CloseableResource クラスと以前の Finalizable クラスの唯一の違いが、ファイナライザ定義ではなく AutoCloseable インタフェースの実装であることを理解するのは難しくありません。

CloseableResource close メソッドの本体は、クラス Finalizable のファイナライザの本体とほぼ同じです。

次のテスト方法は、入力ファイルを読み取り、そのジョブが終了した後にリソースを解放します。

@Test
public void whenTryWResourcesExits__thenResourceClosed() throws IOException {
    try (CloseableResource resource = new CloseableResource()) {
        String firstLine = resource.readFirstLine();
        assertEquals("baeldung.com", firstLine);
    }
}

上記のテストでは、 CloseableResource インスタンスがtry-with-resourcesステートメントの try ブロックに作成されているため、try-with-resourcesブロックの実行が完了するとそのリソースは自動的に閉じられます。

与えられたテストメソッドを実行すると、 CloseableResource クラスの close メソッドからメッセージが出力されます。

5結論

このチュートリアルでは、Javaの中心的な概念である finalize メソッドに焦点を当てました。これは紙の上では便利に見えますが、実行時に見苦しい副作用をもたらす可能性があります。そして、さらに重要なことには、ファイナライザーを使用する代わりの解決策が常にあります。

注意すべき重要な点の1つは、 finalize がJava 9以降廃止予定となっており、最終的に削除されることです。

いつものように、このチュートリアルのソースコードはhttps://github.com/eugenp/tutorials/tree/master/core-java-lang[over on GitHub]にあります。

前の投稿:高度なHttpClient設定
次の投稿:Angular付きのSpring Securityログインページ