尽管这个问题已经有10年了,但我今天也在思考这个问题,并决定实际尝试一下。 :-)
- Linux版本 Linux version 5.10.0-0.bpo.9-amd64
- JVM版本 OpenJDK Runtime Environment (build 11.0.14+9-post-Debian-1deb10u1)
使用这个小测试程序:
import java.util.*;
public class OOMTest {
public static void main(String... atgs){
var list = new ArrayList<String>();
while(true){
list.add(new String("abc"));
}
}
}
在一台拥有4G RAM和4G交换空间的机器上(这只是我的NAS :-)):
tomi@unyanas:~/workspace$ free -h
total used free shared buff/cache available
Mem: 3.7Gi 980Mi 2.4Gi 27Mi 355Mi 2.4Gi
Swap: 3.7Gi 2.2Gi 1.5Gi
tomi@unyanas:~/workspace$ java -Xmx1G OOMTest
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at OOMTest.main(OOMTest.java:9)
- 当允许使用10G堆时,那么:
- 进程会按照上述方式启动,尽管机器上没有那么多RAM+swap
- 但输出明显不是OOM,进程只是被内核杀死:
tomi@unyanas:~/workspace$ java -Xmx10G OOMTest
Killed
总结一下:
- 当主机内存不足时,会出现OOM,但至少不是必然的(我尝试了3次,其中有3次出现了这种情况)
- 使用-Xmx超过机器容量是允许的
- 似乎只有在进程的“cap”为-Xmx值(默认或明确指定)时才能保证出现OOM,否则将有更多的空闲内存(RAM+swap)留给操作系统
对于上述情况,可以说这种情况是由于测试代码具有无限的内存占用,因此我还想尝试一下是否可以通过设置过高的-Xmx值使具有高生成率但内存占用有限的进程失败。因此,如果GC被愚弄以相信实际可用的内存比实际上多得多,并最终被杀死,或者如果它将被内核通知有关失败的OS级内存分配并因此限制堆大小。答案是可以愚弄它。
我已经像这样修改了上面的代码:
import java.util.*;
public class OOMTest {
public static void main(String... atgs){
var list = new ArrayList<String>();
while(true){
list.add(new String("abc"));
if (list.size() > 50000000){
list.remove(list.size() - 1);
}
}
}
}
当指定一个机器可以处理的-Xmx值时,程序可以运行任何长时间(好吧,我真的让它运行了很长时间,直到我吃晚饭,但你懂我的意思 :-))
因此,这永远不会退出(启用GC日志记录后,一旦达到2G堆大小,就可以观察到一个漂亮的重复模式):
java -Xmx2G OOMTest
但是当使用Xmx10G运行时,进程再次被杀死,没有OOM:
tomi@unyanas:~/workspace$ java -Xmx10G OOMTest
Killed
这表明,当JVM尝试分配比当前可用的RAM+swap更多的内存时,它得到的唯一“建设性反馈”就是类似于kill -9的东西。因此,通过使用过高的-Xmx值,本来可以正常运行的进程可能会失败。这绝不意味着在所有操作系统、JVM实现甚至仅仅是GC算法(我使用的是默认的G1)上都会发生这种情况,但在上述设置中肯定是这样的。