Java Runtime.getRuntime().exec() alternatives

我有一个在tomcat下运行的Web应用程序的集合。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() 有哪些替代方案?我只能想到两个,两者都不是很可取。JNI(非常不可取)或重写我们在java中调用的程序,并使其成为Web应用程序以某种方式与之通信的自己的进程。必须有其他人。

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

服务器正在运行 java 6。

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

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


答案 1

在本文中找到了一个解决方法,基本上这个想法是,您在应用程序启动的早期创建一个进程,并与之通信(通过输入流),然后该子进程为您执行命令。

//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

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