我知道每个对象都需要堆内存,堆栈上的每个原语/引用都需要堆栈内存。
当我试图在堆上创建一个对象,而内存不足时,JVM会在堆上创建一个java.lang.OutOfMemoryError,并将它抛出给我。
因此,这意味着JVM在启动时保留了一些内存。
当这个保留内存用完(它肯定会用完,请阅读下面的讨论)并且JVM在堆上没有足够的内存来创建java.lang.OutOfMemoryError实例时会发生什么?
它只是挂着吗?或者他会给我一个空,因为没有内存新的OOM实例?
try {
Object o = new Object();
// and operations which require memory (well.. that's like everything)
} catch (java.lang.OutOfMemoryError e) {
// JVM had insufficient memory to create an instance of java.lang.OutOfMemoryError to throw to us
// what next? hangs here, stuck forever?
// or would the machine decide to throw us a "null" ? (since it doesn't have memory to throw us anything more useful than a null)
e.printStackTrace(); // e.printStackTrace() requires memory too.. =X
}
==
为什么JVM不能预留足够的内存?
无论保留了多少内存,如果JVM没有办法“回收”内存,仍然有可能用完内存:
try {
Object o = new Object();
} catch (java.lang.OutOfMemoryError e) {
// JVM had 100 units of "spare memory". 1 is used to create this OOM.
try {
e.printStackTrace();
} catch (java.lang.OutOfMemoryError e2) {
// JVM had 99 units of "spare memory". 1 is used to create this OOM.
try {
e.printStackTrace();
} catch (java.lang.OutOfMemoryError e3) {
// JVM had 98 units of "spare memory". 1 is used to create this OOM.
try {
e.printStackTrace();
} catch (java.lang.OutOfMemoryError e4) {
// JVM had 97 units of "spare memory". 1 is used to create this OOM.
try {
e.printStackTrace();
} catch (java.lang.OutOfMemoryError e5) {
// JVM had 96 units of "spare memory". 1 is used to create this OOM.
try {
e.printStackTrace();
} catch (java.lang.OutOfMemoryError e6) {
// JVM had 95 units of "spare memory". 1 is used to create this OOM.
e.printStackTrace();
//........the JVM can't have infinite reserved memory, he's going to run out in the end
}
}
}
}
}
}
或者更简洁地说:
private void OnOOM(java.lang.OutOfMemoryError e) {
try {
e.printStackTrace();
} catch (java.lang.OutOfMemoryError e2) {
OnOOM(e2);
}
}
有趣的问题:-)。虽然其他人已经给出了很好的理论方面的解释,我决定尝试一下。这是在Oracle JDK 1.6.0_26, Windows 7 64位。
测试设置
我写了一个简单的程序来耗尽内存(见下文)。
程序只是创建一个静态的java.util。列表,并不断向其中填充新的字符串,直到抛出OOM。然后它捕获它,并继续在一个无休止的循环中填充(可怜的JVM…)
测试结果
从输出中可以看到,抛出OOME的前四次都带有堆栈跟踪。在此之后,如果调用printStackTrace(),后续oome只打印Java .lang. outofmemoryerror: Java堆空间。
因此,JVM显然会尽可能地输出堆栈跟踪,但如果内存非常紧张,它就会像其他答案所建议的那样忽略跟踪。
同样有趣的是OOME的哈希代码。注意,前几个OOME都有不同的哈希值。一旦JVM开始省略堆栈跟踪,散列总是相同的。这表明JVM将使用fresh(预分配?)尽可能长地使用OOME实例,但如果出现紧急情况,它将重用相同的实例,而不是没有任何可抛出的实例。
输出
注意:我截断了一些堆栈跟踪,以使输出更容易阅读(“[…]”)。
iteration 0
iteration 100000
iteration 200000
iteration 300000
iteration 400000
iteration 500000
iteration 600000
iteration 700000
iteration 800000
iteration 900000
iteration 1000000
iteration 1100000
iteration 1200000
iteration 1300000
iteration 1400000
iteration 1500000
Ouch: java.lang.OutOfMemoryError: Java heap space; hash: 1069480624
Keep on trying...
java.lang.OutOfMemoryError: Java heap space
at java.util.Arrays.copyOf(Unknown Source)
at java.util.Arrays.copyOf(Unknown Source)
at java.util.ArrayList.ensureCapacity(Unknown Source)
at java.util.ArrayList.add(Unknown Source)
at testsl.Div.gobbleUpMemory(Div.java:23)
at testsl.Div.exhaustMemory(Div.java:12)
at testsl.Div.main(Div.java:7)
java.lang.OutOfMemoryError: Java heap space
at java.util.Arrays.copyOf(Unknown Source)
[...]
Ouch: java.lang.OutOfMemoryError: Java heap space; hash: 616699029
Keep on trying...
java.lang.OutOfMemoryError: Java heap space
at java.util.Arrays.copyOf(Unknown Source)
[...]
Ouch: java.lang.OutOfMemoryError: Java heap space; hash: 2136955031
Keep on trying...
java.lang.OutOfMemoryError: Java heap space
at java.util.Arrays.copyOf(Unknown Source)
[...]
Ouch: java.lang.OutOfMemoryError: Java heap space; hash: 1535562945
Keep on trying...
java.lang.OutOfMemoryError: Java heap space
Ouch: java.lang.OutOfMemoryError: Java heap space; hash: 1734048134
Keep on trying...
Ouch: java.lang.OutOfMemoryError: Java heap space; hash: 1734048134
Keep on trying...
java.lang.OutOfMemoryError: Java heap space
Ouch: java.lang.OutOfMemoryError: Java heap space; hash: 1734048134
Keep on trying...
[...]
这个项目
public class Div{
static java.util.List<String> list = new java.util.ArrayList<String>();
public static void main(String[] args) {
exhaustMemory();
}
private static void exhaustMemory() {
try {
gobbleUpMemory();
} catch (OutOfMemoryError e) {
System.out.println("Ouch: " + e+"; hash: "+e.hashCode());
e.printStackTrace();
System.out.println("Keep on trying...");
exhaustMemory();
}
}
private static void gobbleUpMemory() {
for (int i = 0; i < 10000000; i++) {
list.add(new String("some random long string; use constructor to force new instance"));
if (i % 10000000== 0) {
System.out.println("iteration "+i);
}
}
}
}
Graham Borland似乎是对的:至少我的JVM显然重用了OutOfMemoryErrors。为了测试这一点,我写了一个简单的测试程序:
class OOMTest {
private static void test (OutOfMemoryError o) {
try {
for (int n = 1; true; n += n) {
int[] foo = new int[n];
}
} catch (OutOfMemoryError e) {
if (e == o)
System.out.println("Got the same OutOfMemoryError twice: " + e);
else test(e);
}
}
public static void main (String[] args) {
test(null);
}
}
运行它会产生如下输出:
$ javac OOMTest.java && java -Xmx10m OOMTest
Got the same OutOfMemoryError twice: java.lang.OutOfMemoryError: Java heap space
顺便说一句,我正在运行的JVM (Ubuntu 10.04)是这样的:
$ java -version
java version "1.6.0_26"
Java(TM) SE Runtime Environment (build 1.6.0_26-b03)
Java HotSpot(TM) 64-Bit Server VM (build 20.1-b02, mixed mode)
编辑:我试着看看如果我使用以下程序强制JVM完全耗尽内存会发生什么:
class OOMTest2 {
private static void test (int n) {
int[] foo;
try {
foo = new int[n];
test(n * 2);
}
catch (OutOfMemoryError e) {
test((n+1) / 2);
}
}
public static void main (String[] args) {
test(1);
}
}
事实证明,它似乎是永远循环的。然而,奇怪的是,尝试用Ctrl+C终止程序却不起作用,而只给出以下消息:
Java HotSpot(TM) 64位服务器虚拟机警告:Java .lang. outofmemoryerror发生异常,向处理器发送SIGINT信号-虚拟机可能需要强制终止