35

我有一组在 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) 是否可以取消对等于父进程在物理内存中空闲的内存量的要求? 我正在寻找一个答案,它允许我指定子进程获得多少内存或允许 Linux 上的 java 访问交换内存。

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

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

服务器正在运行 java 6。

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

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

4

6 回答 6

7

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

//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);
         }
    }
}

我们所做的基本上是为您的应用程序制作一个小的“执行”服务器。如果您在应用程序的早期初始化 ProcessHelper 类,它将成功创建此进程,然后您只需将命令传递给它,因为第二个进程要小得多,它应该始终成功。

您还可以使您的协议更深入一点,例如返回退出代码、通知错误等等。

于 2010-05-20T21:02:16.470 回答
5

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

于 2010-05-20T20:07:34.527 回答
3

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

更新:这已经在From what Linux kernel/libc version is Java Runtime.exec() 对内存安全?

于 2010-05-20T20:13:52.763 回答
1

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

于 2011-09-12T17:41:00.303 回答
1

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

于 2013-08-23T15:33:24.903 回答
1

Another alternative is to have a separate exec process running that watches either a file or some other type of "queue". You append your desired command to that file, and the exec server spots it, running the command, and somehow writing the results to another location you can get.

于 2012-08-17T19:39:09.960 回答