Hatena::ブログ(Diary)

Kazumi007の日記 このページをアンテナに追加 RSSフィード

2009-09-30

[tech][Java]Unix系JavaでRuntime.execをつかうときの落とし穴

JavaではJVMライブラリの実装がWindowsMacOSLinux等で共通の実装だけを採用する方針だったりします。

なので、シンボリックリンクをはったりといったような、使いたくなるような気の利いたOSの機能はAPIでは用意されていません。しかたがないので、lnコマンドといったような外部コマンドを呼び出したい気分になるのですが、UnixJavaでRuntime.exec()を使って外部コマンドを呼び出す際には注意が必要です。意外な落とし穴が待っています。

もし今サーバーUnix系OSJavaでRuntime.exec()を使おうと思っているなら、今すぐ別の方法を検討してください。後でとんでもない目にあいますよ。

いったいどういうことなんでしょうか?とりあえず、こんなプログラムを考えてみます。

package org.kazumi007;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;

public class ExecuteCommand {

	public static void main(String[] args) {
		// 400Mbyte確保
		int[] list = new int[2000000];
		list[0] = 1;
		String cmd = "sleep 1";
		while (true) {
			try {
				Process process = Runtime.getRuntime().exec(cmd);
				InputStream is = process.getInputStream();
				BufferedReader br = new BufferedReader(
						new InputStreamReader(is));
				String line = null;
				while ((line = br.readLine()) != null) {
					// なにもしない
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
		}

	}
}

内容は単純です。巨大な配列を確保してsleepコマンドを1秒おきに起動しているだけの単純なプログラムです。これのどこが問題なのでしょうか?

実行中のJavaプロセスのメモリをみてみましょう・・・。何度か取得した結果の一部です。

$ ps -a -o vsz,comm,args,pid,ppid |grep java

75732 grep grep java 22292 20554

1300640 /usr/bin/java /usr/bin/java -Xmx512m org.kazumi007.ExecuteCommand 22086 10150

1300640 /usr/bin/java /usr/bin/java -Xmx512m org.kazumi007.ExecuteCommand 22290 22086

OSX 10.6.1のjava version "1.6.0_15"でテストしました。

同じメモリサイズのJavaプロセスが2つありますね。なにこれ?!

実はUnix系のJVMはRuntime.execが実行されると、内部でfork()を呼び出しプロセスのコピーを作成し、exec()でコマンドを実行しているのです。この実装のために、コマンドを生成すると必ず起動元のプロセスと同じサイズの空きメモリがなければなりません。さらに自体を悪くするのが、Windowsでは実装が異なるので通常開発環境では問題にならないのです。本番ではOutOfMemoryErrorが出るんだけど、再現しない・・・。ということになってしまうのです。

大切なことなので、もう一度書きます。

UnixJavaでRuntime.execを使うなら、起動元のプロセスで使用しているメモリと同量の空き容量が必要です。可能な限り、Javaから外部コマンドを呼び出さずに別の方法を検討しましょう。

これってJavaで開発するときの常識なんですか?

keserakesera 2009/11/26 16:37 大切な情報ありがとうございます。
まさにこの罠に引っかかってました。

fatrowfatrow 2011/02/22 05:36 こんにちは。
OSX 10.6.5 java 1.6.0_22 の環境で実験してみましたが
java の Runtime.exec から起動した sleep と、シェルから起動した sleep は同じメモリ使用量でした。
Runtime.exec でコマンドを実行しても java のプロセスが一つ増えるということもありませんでした。

スパム対策のためのダミーです。もし見えても何も入力しないでください
ゲスト


画像認証

トラックバック - http://d.hatena.ne.jp/Kazumi007/20090930/1254319746