Javaで「Runtime.exec()」を使って「Too many open Files」を起こしてしまった場合の対処
CentOS上でJavaで指定した回数分「Runtime.exec()」を使って子プロセスを生成して、
シェルスクリプトを実行していたら「Too many open Files」というエラーメッセージが出力されて、
処理が中断してしまいました。
色々調査した結果、原因は2つありました。
1 Java側で子プロセスのストリームを閉じ忘れていた
Javaで「Runtime.exec()」メソッドを使うと戻り値に「java.lang.Process」を返してくれます。
さらに「Process」は下記のメソッドを持っています。
abstract InputStream | getErrorStream() サブプロセスのエラーストリームを取得します。 |
---|---|
abstract InputStream | getInputStream() サブプロセスの入力ストリームを取得します。 |
abstract OutputStream | getOutputStream() サブプロセスの出力ストリームを取得します。 |
これらの戻り値のストリームを明示的にクローズしないと、ファイルディスクリプタを保持し続けて解放しません。
それによって最終的にファイルディスクリプタが枯渇して例外が発生して、ファイルを操作できなくなります。
実装的にはこんな感じです。
エラー処理とかは適当なのでご容赦下さい。
import java.io.IOException; public class ShellExecute implements Runnable { @Override public void run() { Runtime runtime = Runtime.getRuntime(); Process process = null; try { process = runtime.exec("command"); } catch (IOException e) { e.printStackTrace(); } finally { try { if (process != null) { process.getErrorStream().close(); process.getInputStream().close(); process.getOutputStream().close(); process.destroy(); } } catch (IOException e) { e.printStackTrace(); } } } }
2 CentOSのファイルディスクリプタの数が不足していた
1の対応でストリームを閉じてもまだ同じエラーが発生しました。
もう1つの原因はファイルディスクリプタの数自体が不足していた事です。
テストでわざと処理数を増やしていたのですが、ファイルディスクリプタの最大数自体が足りていませんでした。
これは「ulimit」コマンドを設定することができました。
まずは下記のコマンドで現在の設定値を確認します。
$ ulimit -a
core file size (blocks, -c) 0
data seg size (kbytes, -d) unlimited
scheduling priority (-e) 0
file size (blocks, -f) unlimited
pending signals (-i) 4095
max locked memory (kbytes, -l) 32
max memory size (kbytes, -m) unlimited
open files (-n) 1024
pipe size (512 bytes, -p) 8
POSIX message queues (bytes, -q) 819200
real-time priority (-r) 0
stack size (kbytes, -s) 10240
cpu time (seconds, -t) unlimited
max user processes (-u) 4095
virtual memory (kbytes, -v) unlimited
file locks (-x) unlimited
「open files」の項目が対象の設定値になります。デフォルトは「1024」です。
これを変更するには下記のコマンドで設定します。
ulimit -n ファイルディスクリプタの数
これらの対処をして「Too many open Files」のエラーが発生しなくなりました。
ストリームの閉じ忘れは本当に気をつけないといけないなと改めて思いました。