Javaにおけるリソースクリーンアップとは?Java 8以前でfinalize()をオーバーライドする理由

今日はJavaプログラムを書きました。このプログラムは入力ストリームと出力ストリームに関するものです。

github.com

Javaにおけるリソースクリーンアップとは?Java 8以前でfinalize()をオーバーライドする理由


1. Javaのリソースクリーンアップとは?

Javaにおいて、リソースクリーンアップ(Resource Cleanup) とは、プログラムが使用しなくなったリソースを適切に解放することを指します。これを行わないと、リソースリーク(Resource Leak) が発生し、アプリケーションのパフォーマンスが低下したり、最悪の場合クラッシュする可能性があります。

Javaガベージコレクタ(GC はメモリ管理を自動で行いますが、メモリ以外のリソース(ファイル、データベース接続、ネットワーク接続など)は手動で解放する必要があります

特にクリーンアップが必要なリソース: - ファイルストリームFileInputStreamFileOutputStream など) - データベース接続ConnectionStatementResultSet など) - ネットワーク接続SocketHttpURLConnection など) - スレッドリソースExecutorService など)


2. なぜ Java 8 以前では finalize() をオーバーライドする必要があったのか?

Java 8 以前では、開発者は finalize() メソッドをオーバーライドし、オブジェクトがガベージコレクションGC)によって破棄される前にリソースを解放するために使用していました。

Java はメモリの自動管理が可能ですが、ファイルやネットワーク接続などの非メモリリソースは自動的に解放されません。そのため、finalize() を利用して、リソースが不要になったタイミングで明示的に解放することが一般的でした。

例:finalize() でファイルを閉じる

import java.io.*;

class FileHandler {
    private FileInputStream file;

    FileHandler(String fileName) throws FileNotFoundException {
        this.file = new FileInputStream(fileName);
        System.out.println("ファイルが開かれました。");
    }

    @Override
    protected void finalize() throws Throwable {
        if (file != null) {
            file.close();
            System.out.println("finalize() でファイルを閉じました。");
        }
    }
}

public class Main {
    public static void main(String[] args) throws FileNotFoundException {
        FileHandler handler = new FileHandler("test.txt");
        handler = null;  // 参照を破棄
        System.gc();  // GCをリクエスト

        try { Thread.sleep(1000); } catch (InterruptedException e) {}
    }
}

出力(例):

ファイルが開かれました。
finalize() でファイルを閉じました。

この例では、finalize() メソッドがオブジェクトのGC回収前に呼ばれ、ファイルを閉じる処理が実行されます


3. finalize() の問題点

finalize() はリソースクリーンアップに使用できますが、多くの重大な欠点があります。
そのため、Java 9 で非推奨(deprecated) になりました。

(1) finalize() の実行タイミングが不確実

  • finalize()ガベージコレクションGC)が発生したときに実行される ため、いつ実行されるか不確実 です。
  • そのため、リソースの解放が 長時間遅れる可能性 があり、リソースリーク につながることがあります。

例:finalize() の実行タイミングが不確実

class ExpensiveResource {
    @Override
    protected void finalize() throws Throwable {
        System.out.println("リソースが `finalize()` でクリーンアップされました。");
    }
}

public class Main {
    public static void main(String[] args) {
        new ExpensiveResource(); // 参照を保存しない
        System.out.println("main メソッドの終了");
        // GCが発生しなければ、finalize() は実行されないかもしれない
    }
}

出力(例):

main メソッドの終了

finalize() は呼ばれない可能性があり、リソースが適切に解放されない!


(2) finalize() はパフォーマンスに悪影響を与える

  • finalize() をオーバーライドしたオブジェクトは GCに2回スキャンされる ため、処理が遅くなります。
  • finalize() 内で重い処理をすると、アプリケーションのパフォーマンスが大幅に低下 します。

例:finalize() による遅延

class SlowFinalizer {
    @Override
    protected void finalize() throws Throwable {
        Thread.sleep(2000); // 2秒間スリープ
        System.out.println("`finalize()` 実行");
    }
}

public class Main {
    public static void main(String[] args) {
        new SlowFinalizer();
        System.gc();
        System.out.println("GC をリクエストしました。");
    }
}

出力(例):

GC をリクエストしました。
(2秒遅れて出力)
`finalize()` 実行

問題点: - finalize() が遅いと、GCがブロックされ、アプリ全体の処理が遅くなる。


4. finalize() の代替方法(Java 8 以降の推奨)

(1) try-with-resources(推奨)

  • AutoCloseable を実装したリソースは、try-with-resources を使うと確実にクリーンアップ される。
class Resource implements AutoCloseable {
    public void use() {
        System.out.println("リソースを使用中...");
    }

    @Override
    public void close() {
        System.out.println("リソースを解放しました。");
    }
}

public class Main {
    public static void main(String[] args) {
        try (Resource res = new Resource()) {
            res.use();
        } // ここで自動的に close() が呼ばれる
    }
}

出力:

リソースを使用中...
リソースを解放しました。

メリット: - 即時リソース解放(遅延なし)。 - GCに依存しないので、確実に実行される。


(2) CleanerJava 9+)

Java 9 以降では、Cleaner クラスを使うことで、安全なリソースクリーンアップが可能。

import java.lang.ref.Cleaner;

class Cat {
    private static final Cleaner cleaner = Cleaner.create();
    private final Cleaner.Cleanable cleanable;
    private final String name;

    Cat(String name) {
        this.name = name;
        cleanable = cleaner.register(this, () -> System.out.println(name + " をクリーンアップしました。"));
    }
}

メリット: - finalize() より高効率。 - GCの動作を妨げない。


5. まとめ

  • Java 8 以前finalize() を使ってリソースを解放していたが、不確実で低速なため非推奨。
  • Java 8 以降では、try-with-resourcesCleaner を使用すべき。
    🚀 finalize() はもう使わず、最新のクリーンアップ方法を活用しよう!