web-dev-qa-db-ja.com

Java Runtime.getRuntime()。exec()の代替

Tomcatで実行されているwebappsのコレクションがあります。 Tomcatは、-Xmx引数を使用して最大2 GBのメモリを搭載するように構成されています。

多くのWebアプリケーションは、次のコードを使用するタスクを実行する必要があります。

Runtime runtime = Runtime.getRuntime();
Process process = runtime.exec(command);
process.waitFor();
...

私たちが抱えている問題は、この「子プロセス」がLinux(Redhat 4.4およびCentos 5.4)で作成される方法に関連しています。

この子プロセスを作成するには、最初に、Tomcatが使用している量と同じ量のメモリを物理(非スワップ)システムメモリのプールで解放する必要があることを理解しています。十分な空き物理メモリがない場合、次のようになります。

    Java.io.IOException: error=12, Cannot allocate memory
     at Java.lang.UNIXProcess.<init>(UNIXProcess.Java:148)
     at Java.lang.ProcessImpl.start(ProcessImpl.Java:65)
     at Java.lang.ProcessBuilder.start(ProcessBuilder.Java:452)
     ... 28 more

私の質問は:

1)物理メモリで解放されている親プロセスと同じ量のメモリの要件を削除することは可能ですか?子のメモリ量を指定できる回答を探していますプロセスは、LinuxでJavaがスワップメモリ​​にアクセスすることを許可または許可します。

2)#1の解決策がない場合、Runtime.getRuntime()。exec()の代替手段は何ですか? 2つしか考えられませんでしたが、どちらも非常に望ましいものではありません。 JNI(非常に望ましくない)またはJavaで呼び出すプログラムを書き換えて、Webアプリケーションが何らかの方法で通信するのを独自のプロセスにします。他にもある必要があります。

3)この問題には別の面があり、修正できない可能性があるとは思いませんか? Tomcatが使用するメモリの量を減らすことはオプションではありません。サーバーのメモリを増やすことは常にオプションですが、よりバンドエイドのように見えます。

サーバーは実行中Java 6。

編集:私はTomcat固有の修正を探していないことを指定する必要があります。この問題は、JavaアプリケーションがWebサーバーで実行されている場合に発生します(複数あります)。Tomcatを例として使用しました。最初に実際にエラーを確認した場所であり、再現可能なエラーです。

編集:最後に、Javaでシステムコールが行っていたことを書き直すことで、この問題を解決しました。追加のシステムコールを行わずにこれを実行できたのはかなり幸運だったと思います。すべてのプロセスがこれを実行できるわけではないので、これに対する実際の解決策を見てみたいと思います。

34
twilbrand

私はこれに回避策を見つけました article 、基本的には、アプリケーションの起動の早い段階で(入力ストリームを介して)通信するプロセスを作成し、そのサブプロセスがコマンドを実行することです。

//you would probably want to make this a singleton
public class ProcessHelper
{
    private OutputStreamWriter output;
    public ProcessHelper()
    {
        Runtime runtime = Runtime.getRuntime();
        Process process = runtime.exec("Java ProcessHelper");
        output = new OutputStreamWriter(process.getOutputStream());
    }
    public void exec(String command)
    {
        output.write(command, 0, command.length());
    }
}

次に、ヘルパーを作成しますJavaプログラム

public class ProcessHelper
{
    public static void main(String[] args)
    {
         BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
         String command;
         while((command = in.readLine()) != null)
         {
             Runtime runtime = Runtime.getRuntime();
             Process process = runtime.exec(command);
         }
    }
}

基本的に私たちがやったことはあなたのアプリケーションのための小さな「exec」サーバーを作ることです。アプリケーションの早い段階でProcessHelperクラスを初期化すると、このプロセスが正常に作成されます。2番目のプロセスはずっと小さいため、2番目のプロセスは常に成功するため、コマンドをパイプでパイプするだけです。

また、終了コードを返す、エラーを通知するなど、プロトコルをもう少し深くすることもできます。

6
luke

ProcessBuilder を使用してみてください。ドキュメントは、それが最近サブプロセスを開始するための「推奨される」方法であると言います。また、環境マップ(ドキュメントはリンクにあります)を使用して、新しいプロセスのメモリ許容量を指定することも検討してください。大量のメモリが必要な理由は、Tomcatプロセスから設定を継承しているためだと思います(ただし、確実ではありません)。環境マップを使用すると、その動作をオーバーライドできます。ただし、プロセスの起動はOS固有であるため、YMMVであることに注意してください。

5
Ian McLaird

私は考えるこれはUNIXのfork()の問題であり、メモリ要件はfork()が機能する方法から生じます-最初に子プロセスイメージをクローンします(現在のサイズにかかわらず) )次に、親イメージを子イメージに置き換えます。 Solarisではこの動作を制御する方法がいくつかあることはわかっていますが、それが何であるかはわかりません。

更新:これはすでに Linuxカーネル/ libcのバージョンからJava Runtime.exec ()メモリに関して安全ですか?

2
Justin

これは私が考えるのを助けるでしょう。私はこれが古いスレッドであることを知っています、将来の参考のために... http://wrapper.tanukisoftware.com/doc/english/child-exec.html

1
Kam

別の方法として、ファイルまたは他のタイプの「キュー」を監視する別の実行プロセスを実行する方法があります。目的のコマンドをそのファイルに追加すると、execサーバーがそれを見つけてコマンドを実行し、結果を取得できる別の場所に結果を書き込みます。

1
gregturn

これまでに見つけた最善の解決策は、公開鍵で安全なシェルを使用することでした。 http://www.jcraft.com/jsch/ を使用して、localhostへの接続を作成し、そのようなコマンドを実行しました。多分それは少しオーバーヘッドがありますが、私の状況ではそれは魅力のように働きました。

1
JoG