【问题标题】:Java Runtime.getRuntime().exec() alternativesJava Runtime.getRuntime().exec() 替代品
【发布时间】:2011-02-22 01:08:44
【问题描述】:

我有一组在 tomcat 下运行的 web 应用程序。使用 -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 中所做的事情解决了这个问题。我觉得我们很幸运能够在不进行额外系统调用的情况下做到这一点。并非所有进程都能够做到这一点,所以我仍然希望看到一个实际的解决方案。

【问题讨论】:

  • 我实际上并没有在那篇文章中看到任何回答我问题的内容。我在那里看到的一个动作是“减少父进程使用的内存量”(对我们来说不是一个选项),无论是使用 ulimit 还是 java opts。另一个与上面卢克的回答相同,即制作一个使用更少内存的单独进程。这远非理想,但至少是合理的
  • 您使用的是什么 JVM(Sun、OpenJDK...?)这没有帮助吗? stackoverflow.com/questions/1124771/…
  • 这是上面提到的 linux 操作系统上的 Sun JVM。在那篇文章中,原始发帖人说他用“echo 0 > /proc/sys/vm/overcommit_memory”修复了它,并请人解释原因。从来没有人这样做过。在我有更多关于它的信息之前,我对使用这种方法持怀疑态度。谷歌搜索对我没有帮助。这个命令可能会回答我的 #1 或 #3,但我正在寻找人来解释它在做什么以及使用它的优点/缺点。
  • 那么,你最后做了什么?我认为这是一个很好的问题。为了我们其他人的利益,请提供您的解决方案(尝试?)来解决这个问题。

标签: java tomcat memory-management runtime.exec


【解决方案1】:

我在这个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);
         }
    }
}

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

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

【讨论】:

  • 这不适用于 web 应用程序。在服务器运行期间随时部署 web 应用程序 - 消耗任意数量的内存。
  • @Arne Burmeister - 是的,如果您在共享的 TomCat 实例上运行它,那么到它运行时,您可能会为进程分配任意大量的内存。但是,如果您在非共享的 TomCat 服务器(这是唯一的应用程序)上运行,并且您可以挂接到应用程序启动以执行它,它应该可以工作。
  • 正如我最近在上面所说的,我不是在寻找特定于 tomcat 的答案。我们的 tomcat 有多个 webapps 以及多个其他 java 进程,它们都可能会遇到相同的问题。
【解决方案2】:

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

【讨论】:

  • 我看过 ProcessBuilder,特别是它修改环境的能力。实际上,您也可以使用 Runtime 来做到这一点。但是,我找不到任何关于我应该更改哪些环境变量以保持初始内存低的信息。我打印了一些进程的当前环境,内存修改参数不明显。您能指出我如何使用环境变量控制内存分配吗?特别是在 Linux 中?
  • 据我了解,ProcessBuilder 在 Linux 上也受到同样的内存限制。
  • 它需要这么多内存的原因是用于分离新进程的标准“fork”算法会导致执行进程暂时完全重复。据说这是唯一跨平台启动子进程的方式。据我所知,ProcessBuilder 只是提供了便利——它实际上并没有解决这个问题。
  • 这到底有什么帮助? ProcessBuilder 是否允许您控制最终启动新进程时将使用的系统调用?这似乎不太可能,因此这并不能解决 OP 的问题。
【解决方案3】:

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

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

【讨论】:

  • 我实际上并没有在那篇文章中看到任何回答我问题的内容。我在那里看到的一个动作是“减少父进程使用的内存量”(对我们来说不是一个选项),无论是使用 ulimit 还是 java opts。另一个与上面卢克的回答相同,即制作一个使用更少内存的单独进程。这远非理想,但至少是合理的。
【解决方案4】:

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

【讨论】:

【解决方案5】:

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

【讨论】:

  • 关键是 exec 消费者进程占用空间小,因此可以毫无问题地分叉。
  • 使用文件或其他公开可见的入口点似乎会带来安全问题,不是吗?让攻击者作为您的用户(在本例中为 tomcat 用户)执行任意代码。具有某种身份验证协议的套接字可能会更好。在实践中,该身份验证协议也可能是 SSH...
【解决方案6】:

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-06-21
    • 1970-01-01
    • 2011-01-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多