はじめに
Java何年もやっててclose忘れてエラー起こしたアホは私です。
発生した問題
開発中のサーバーで連続負荷テストを実施していると、時間がたつにつれてアプリケーションの動作がおかしくなる現象が発生した。
CPUやメモリの使用率に問題は見られず原因が分からないままだったが、1~2週間ほど放置し続けるとログに「もうファイル開けません」的なエラーを出してアプリケーションが死んでいた。
その後アプリケーションが開いているファイルディスクリプタ数を確認すると、1プロセスあたりに開けるファイルディスクリプタ数の上限に達しているのを確認した。
問題となったコード
return Files.list(Paths.get(dirPathStr)).map(path -> path.getFileName().toString())
.collect(Collectors.toSet());
問題となったコードは上記。Stream APIを使用してディレクトリ内のファイル名一覧を返すメソッドだが、Files.list()
でオープンしたディレクトリがcloseされていない。
closeを忘れてもGCによってメモリ自体は解放されると思うがファイルディスクリプタの方は解放されないようなので、実行されるたびにファイルディスクリプタの数が増え続けることになった。
ちなみに開発環境にEclipseを使っていると(Scannerクラス等)ファイルを開くクラスのインスタンスでcloseを忘れると警告を出してくれるが、(Eclipse 4.7時点において)上記コードは警告を出してくれない。何故……
修正したコード
try (Stream<Path> paths = Files.list(Paths.get(dirPathStr))) {
return paths.map(path -> path.getFileName().toString()).collect(Collectors.toSet());
}
ちゃんとcloseするようにした。ファイルディスクリプタの増加も起きなくなり、アプリケーションも問題なく動作するようになった。
おわりに
以下教訓
- テストするときはCPUやメモリ使用率以外も確認する
- ちゃんとJavadoc確認する
- IDEに頼りすぎない