如何解决调用Runtime#exec()时出现的"java.io.IOException: error=12, Cannot allocate memory"错误?

68

在我的系统上,我无法运行一个简单的Java应用程序来启动一个进程。我不知道如何解决。

你能给我一些提示如何解决吗?

程序如下:

[root@newton sisma-acquirer]# cat prova.java
import java.io.IOException;

public class prova {

   public static void main(String[] args) throws IOException {
        Runtime.getRuntime().exec("ls");
    }

}

结果为:

[root@newton sisma-acquirer]# javac prova.java && java -cp . prova
Exception in thread "main" java.io.IOException: Cannot run program "ls": java.io.IOException: error=12, Cannot allocate memory
        at java.lang.ProcessBuilder.start(ProcessBuilder.java:474)
        at java.lang.Runtime.exec(Runtime.java:610)
        at java.lang.Runtime.exec(Runtime.java:448)
        at java.lang.Runtime.exec(Runtime.java:345)
        at prova.main(prova.java:6)
Caused by: java.io.IOException: java.io.IOException: error=12, Cannot allocate memory
        at java.lang.UNIXProcess.<init>(UNIXProcess.java:164)
        at java.lang.ProcessImpl.start(ProcessImpl.java:81)
        at java.lang.ProcessBuilder.start(ProcessBuilder.java:467)
        ... 4 more

系统配置:

[root@newton sisma-acquirer]# java -version
java version "1.6.0_0"
OpenJDK Runtime Environment (IcedTea6 1.5) (fedora-18.b16.fc10-i386)
OpenJDK Client VM (build 14.0-b15, mixed mode)
[root@newton sisma-acquirer]# cat /etc/fedora-release
Fedora release 10 (Cambridge)

编辑:解决方案 这解决了我的问题,我不确切知道为什么:

echo 0 > /proc/sys/vm/overcommit_memory

谁能够解释一下就点个赞吧 :)

附加信息,top 输出:

top - 13:35:38 up 40 min,  2 users,  load average: 0.43, 0.19, 0.12
Tasks: 129 total,   1 running, 128 sleeping,   0 stopped,   0 zombie
Cpu(s):  1.5%us,  0.5%sy,  0.0%ni, 94.8%id,  3.2%wa,  0.0%hi,  0.0%si,  0.0%st
Mem:   1033456k total,   587672k used,   445784k free,    51672k buffers
Swap:  2031608k total,        0k used,  2031608k free,   188108k cached

附加信息,免费输出:

[root@newton sisma-acquirer]# free
             total       used       free     shared    buffers     cached
Mem:       1033456     588548     444908          0      51704     188292
-/+ buffers/cache:     348552     684904
Swap:      2031608          0    2031608

这可能是Linux版本中的一个错误,或者您有一些权限问题。您可以查看源代码中的UnixProcess:164,以找出它尝试分配什么。 - akarnokd
1
你可以随时尝试使用Sun JDK。 - wds
我曾经发布了一个解决你问题的免费库的链接,但是一位版主没有给出任何解释就删除了我的回答。为了造福社区,我再试一次,以评论的形式提供:Yajsw可以解决你的内存问题,在Linux上使用C库调用进程创建。在这里阅读更多信息:http://sourceforge.net/projects/yajsw/forums/forum/810311/topic/4423982 - kongo09
我在使用openjdk时遇到了这个问题,但是当我将其替换为官方的sun jdk后,分叉就正常工作了... 如果您不想替换openjdk,则“overcommit_memory” hack也可以起作用。 - Dzhu
10个回答

37

这是解决方案,但你需要设置:

echo 1 > /proc/sys/vm/overcommit_memory

28
注意!当 overcommit_memory 设为1时,每个 malloc() 调用都会成功分配内存。当系统内存不足时,Linux 将随机关闭进程。http://www.win.tue.nl/~aeb/linux/lk/lk-9.html - Dan Fabulich
1
这是否可以限制为每个进程,而不是整个系统? - Mark McDonald
1
在Vagrant虚拟机中开发时使用此解决方案。 - François Beausoleil
是的,在本地 Vagrant/JDK 环境中尝试构建 dom-distiller 时,这对我也起作用了。我不得不使用 sudo su - 获取 root 权限来调整 proc 文件系统。 - Big Rich

21

你的机器内存状况如何?例如,如果你运行top命令,你有多少可用的空闲内存?

我怀疑UnixProcess执行了一个fork()操作,但它并没有从操作系统获得足够的内存(如果记忆力服务正确,它会通过fork()在新的内存进程中复制进程,然后通过exec()运行ls,但它还没有走到这一步)。

编辑:关于你提出的超限解决方案,它允许超限使用系统内存,可能使进程分配(但不是使用)比实际可用的更多的内存。因此,我想fork()会像下面评论中讨论的那样复制Java进程的内存。当然,你不会使用这些内存,因为'ls'替换了重复的Java进程。


我曾经读到,fork()函数调用实际上会复制当前正在运行的进程的整个内存。这还是正确的吗?如果您有一个占用1.2GB内存且总共2GB的Java程序,我想它会失败? - akarnokd
2
是的,我本来要提到这个,但我模糊地记得现代操作系统会为内存页实现写时复制,所以我不确定。 - Brian Agnew
如果她使用默认设置运行应用程序,我想模拟64MB内存不应该是问题。 - akarnokd
22
我认为安德烈是个男性名字,在意大利是一个男性的名字 :-) - Brian Agnew
@kd304 是的,这仍然是正确的,只有内存映射被复制了 - 并且在新进程中,内存被设置为写时复制 - 这意味着只有在写入时才会实际复制内存。不过,在使用大量内存的大型应用服务器中,这仍然是一个相当大的问题 - 因为这些服务器往往会在 fork 和 exec 之间的短时间窗口内导致大量内存被复制。 - nos

9

Runtime.getRuntime().exec 会为新进程分配与主进程相同的内存。如果你的堆大小设置为1GB并尝试执行该语句,则会再次分配1GB内存供该进程运行。


2
我在使用Maven时遇到了问题。我的机器只有1GB内存,同时运行着Hudson、Nexus和另一个Maven进程。由于我们错误地在MAVEN_OPTS上设置了-Xms512m,导致Maven进程崩溃。将其修正为-Xms128m后问题得以解决。 - Asaf Mesika

9

有没有想法它是否适用于OpenJDK或等效的非Sun JVM? - Mark McDonald
升级到1.6.0_37-b06后,我没有遇到这个问题。对于错误修复仍然感到困惑。那么JVM为Runtime.exec分配了多少内存? - Satish Pandey
很好的观点。升级JVM确实可以解决问题,因为它们现在使用不同(更轻量级)的系统调用。 - neesh
1
仍然在1.7.0_91版本中遇到此问题,似乎更多是我的机器内存限制(当其他应用程序关闭时,我不会遇到此错误)。另外,exec生成具有与原始进程相同的RAM使用量的新进程。 - Karussell
@Karussell:你解决了这个问题吗?我也遇到了同样的问题,我的版本是1.7.0_111。升级到jdk8不是一个选项。 - saurabheights

8

我用JNA解决了这个问题:https://github.com/twall/jna

import com.sun.jna.Library;
import com.sun.jna.Native;
import com.sun.jna.Platform;

public class prova {

    private interface CLibrary extends Library {
        CLibrary INSTANCE = (CLibrary) Native.loadLibrary((Platform.isWindows() ? "msvcrt" : "c"), CLibrary.class);
        int system(String cmd);
    }

    private static int exec(String command) {
        return CLibrary.INSTANCE.system(command);
    }

    public static void main(String[] args) {
        exec("ls");
    }
}

8

5
如果您查看java.lang.Runtime的源代码,您会发现exec最终调用了受保护的方法:execVM,这意味着它使用虚拟内存。因此,对于类Unix系统,虚拟内存取决于交换空间的数量+一定比例的物理内存。
Michael的答案确实解决了您的问题,但它可能(或者说,最终会)导致操作系统死锁的内存分配问题,因为1表明操作系统更少关注内存分配,而0只是猜测,很明显,您很幸运,操作系统猜测您可以有这些内存。下一次呢?嗯.....
更好的方法是您需要尝试自己的情况,并提供足够的交换空间和更好的物理内存使用比率,并将值设置为2而不是1或0。

4

overcommit_memory

控制系统内存的过度承诺,可能允许进程分配(但不使用)比实际可用内存更多的内存。

0 - 启用启发式过度承诺处理。拒绝明显的地址空间过度承诺。适用于典型系统。它确保严重的野生分配失败,同时允许过度承诺减少交换使用量。在此模式下,root允许分配略多一些内存。这是默认设置。

1 - 始终过度承诺。适用于某些科学应用程序。

2 - 不要过度承诺。系统的总地址空间提交不得超过交换加上可配置百分比(默认为50)的物理RAM。根据您使用的百分比,在大多数情况下,这意味着在尝试使用已分配内存时,进程不会被终止,而将按适当方式收到有关内存分配的错误。


4
尽管听起来很奇怪,但一个解决办法是减少JVM分配的内存量。由于fork()会复制进程及其内存,如果您的JVM进程实际上不需要通过-Xmx分配的那么多内存,则可为git分配内存。
当然,您也可以尝试本文中提到的其他解决方案(如过度提交或升级已修复该问题的JVM版本)。如果您迫切需要一种不影响环境且保持所有软件完好的解决方案,可以尝试减少内存使用量。但请记住,过于激进地减少-Xmx可能会导致OOM。我建议长期稳定的解决方案是升级JDK。

4

Tanuki包装器非常令人印象深刻。不幸的是,WrapperManager是专业版的一部分,如果这是您唯一需要的东西,那么价格相当昂贵。您知道是否有任何免费的替代品吗? - kongo09
@kongo09 它作为免费(GPLv2)社区版的一部分可用。您甚至可以下载源代码并在GPL产品中使用它。 - Dan Fabulich
我认为这不是社区版的一部分。如果您尝试快速测试,您将会得到以下异常:Exception in thread "main" org.tanukisoftware.wrapper.WrapperLicenseError: Requires the Professional Edition. - kongo09

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接