今日はJavaプログラムを書きました。このプログラムは入力ストリームと出力ストリームに関するものです。
Javaにおけるリソースクリーンアップとは?Java 8以前でfinalize()をオーバーライドする理由
1. Javaのリソースクリーンアップとは?
Javaにおいて、リソースクリーンアップ(Resource Cleanup) とは、プログラムが使用しなくなったリソースを適切に解放することを指します。これを行わないと、リソースリーク(Resource Leak) が発生し、アプリケーションのパフォーマンスが低下したり、最悪の場合クラッシュする可能性があります。
Javaのガベージコレクタ(GC) はメモリ管理を自動で行いますが、メモリ以外のリソース(ファイル、データベース接続、ネットワーク接続など)は手動で解放する必要があります。
特にクリーンアップが必要なリソース:
- ファイルストリーム(FileInputStream、FileOutputStream など)
- データベース接続(Connection、Statement、ResultSet など)
- ネットワーク接続(Socket、HttpURLConnection など)
- スレッドリソース(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) Cleaner(Java 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の動作を妨げない。