I/O とリソース管理
はじめに
JVMアプリケーションでは、HTTPコネクションやJDBCコネクションなど、OSレベルのリソースを扱います。これらはGCの管理対象外であり、適切な管理が必要です。
ヒープとヒープ外リソース
┌─────────────────────────────────────────────────────────────────────┐
│ JVMリソースの分類 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ JVMヒープ(GC管理) ヒープ外(GC管理外) │
│ ┌─────────────────────┐ ┌─────────────────────┐ │
│ │ ・Javaオブジェクト │ │ ・TCPソケット │ │
│ │ ・配列 │ │ ・ファイルハンドル │ │
│ │ ・文字列 │ │ ・Direct Buffer │ │
│ │ ・コレクション │ │ ・ネイティブメモリ │ │
│ │ │ │ ・DBコネクション │ │
│ │ 参照がなくなると │ │ │ │
│ │ GCが自動回収 │ │ 明示的に close() │ │
│ │ │ │ しないと解放 されない│ │
│ └─────────────────────┘ └─────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘
リソースの種類
| リソース | Java での表現 | OS リソース |
|---|---|---|
| ファイル | FileInputStream, FileChannel | ファイルディスクリプタ |
| ネットワーク | Socket, ServerSocket | TCPソケット |
| HTTP | HttpURLConnection, HttpClient | TCPソケット + SSL/TLS |
| データベース | Connection, Statement | TCP + DBセッション |
| プロセス | Process | プロセスID, パイプ |
なぜ GC では回収されないのか
┌─────────────────────────────────────────────────────────────────────┐
│ リソース管理の問題 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ Connection conn = dataSource.getConnection(); │
│ │
│ JVMヒープ OS / ネイティブ │
│ ┌─────────────────┐ ┌─────────────────┐ │
│ │ Connection obj │ ───参照───→ │ TCP Socket │ │
│ │ (HikariProxyConn│ │ File Descriptor │ │
│ │ ection) │ │ DB Session │ │
│ └─────────────────┘ └─────────────────┘ │
│ │ │ │
│ ↓ ↓ │
│ GCで回収可能 GCでは回収できない │
│ (参照がなくなれば) (OSに返却が必要) │
│ │
│ 問題: Connection オブジェクトがGCされても、 │
│ OSのソケットは解放されない → リソースリーク │
│ │
└─────────────────────────────────────────────────────────────────────┘
Finalizer の問題
// 昔のアプローチ(非推奨)
public class OldConnection {
private int socketFd;
@Override
protected void finalize() throws Throwable {
// GC時に呼ばれることを期待
closeNativeSocket(socketFd);
}
}
Finalizer が危険な理由:
| 問題 | 説明 |
|---|---|
| 実行タイミング不定 | GCがいつ走るかわからない |
| 実行保証なし | finalize が呼ばれないこともある |
| パフォーマンス劣化 | Finalizer キューの処理オーバーヘッド |
| リソース枯渇 | GC前にOSリソースが枯渇する可能性 |
警告
Java 9+ で非推奨: finalize() は Java 9 で非推奨、Java 18 で削除予定です。代わりに try-with-resources や Cleaner を使用してください。
try-with-resources
基本的な使い方
// 推奨:try-with-resources
try (Connection conn = dataSource.getConnection();
PreparedStatement stmt = conn.prepareStatement(sql);
ResultSet rs = stmt.executeQuery()) {
while (rs.next()) {
// データ処理
}
} // ← 自動的に rs.close(), stmt.close(), conn.close() が呼ばれる
// 例外が発生しても確実にクローズされる