Java Runtime.getRuntime()。exec()替代方案

时间:2010-05-20 19:04:16

标签: java tomcat memory-management runtime.exec

我有一组在tomcat下运行的webapps。使用-Xmx参数将Tomcat配置为具有多达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)是否可以删除内存量等于父进程在物理内存中空闲的要求?我正在寻找一个允许我指定多少内容的答案内存子进程获取或允许java上的java访问交换内存。

2)如果没有#1的解决方案存在,Runtime.getRuntime()。exec()的替代方法是什么?我只能想到两个,这两个都不是很理想。 JNI(非常不可取)或重写我们在java中调用的程序,并使其成为webapp以某种方式与之通信的自己的进程。必须有其他人。

3)这个问题的另一面是否我没有看到它可能会修复它?降低tomcat使用的内存量是不可取的。增加服务器上的内存总是一种选择,但似乎更像是一种创可贴。

服务器正在运行java 6.

编辑:我应该指定我不是在寻找特定于tomcat的修复程序。我们在Web服务器上运行的任何java应用程序都可以看到这个问题(有多个)。我只是使用tomcat作为一个例子,因为它很可能会分配最多的内存,而这正是我们第一次看到错误的地方。这是一个可重现的错误。

编辑:最后,我们通过重写系统调用在java中执行的操作来解决此问题。我觉得我们很幸运能够在不进行额外系统调用的情况下完成此操作。并非所有流程都能够做到这一点,所以我仍然希望看到一个实际的解决方案。

6 个答案:

答案 0 :(得分:6)

我在这个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类,它将成功创建此过程,然后您只需将命令传递给它,因为第二个过程要小得多,它应该总是成功。

您还可以更深入地制定协议,例如返回exitcodes,通知错误等等。

答案 1 :(得分:5)

尝试使用ProcessBuilder。 Docs说这是现在启动子流程的“首选”方式。您还应该考虑使用环境贴图(文档在链接中)来指定新进程的内存容量。我怀疑(但不确定)它需要如此多内存的原因是它继承了tomcat进程的设置。使用环境贴图应该允许您覆盖该行为。但请注意,启动流程非常符合操作系统,因此YMMV。

答案 2 :(得分:2)

认为这是一个unix fork()问题,内存需求来自fork()的工作方式 - 它首先克隆子进程映像(无论当前大小如何)然后用子图像替换父图像。我知道在Solaris上有一些控制这种行为的方法,但我不知道它是什么。

更新:这已在From what Linux kernel/libc version is Java Runtime.exec() safe with regards to memory?

中说明

答案 3 :(得分:1)

这有助于我思考。我知道这是一个旧线程,仅供将来参考...  http://wrapper.tanukisoftware.com/doc/english/child-exec.html

答案 4 :(得分:1)

另一种方法是运行一个单独的exec进程来监视文件或其他类型的“队列”。您将所需的命令附加到该文件,并且exec服务器发现它,运行命令,并以某种方式将结果写入您可以获得的其他位置。

答案 5 :(得分:1)

到目前为止我发现的最佳解决方案是使用带有公钥的安全shell。使用http://www.jcraft.com/jsch/我创建了与localhost的连接并执行了这样的命令。也许它有更多的开销,但对于我的情况,它就像一个魅力。