我知道每个对象都需要堆内存,堆栈上的每个原语/引用都需要堆栈内存。
当我试图在堆上创建一个对象,而内存不足时,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在启动时这样做:
private static final OutOfMemoryError OOME = new OutOfMemoryError();
稍后,JVM执行以下Java字节码之一:'new', 'anewarray',或'multianewarray'。这条指令导致JVM在内存不足的情况下执行一些步骤:
Invoke a native function, say allocate(). allocate() attempts to allocate memory for some a new instance of a particular class or array.
That allocation request fails, so the JVM invokes another native function, say doGC(), which attempts to do garbage collection.
When that function returns, allocate() tries to allocate memory for the instance once again.
If that fails(*), then the JVM, within allocate(), simply does a throw OOME;, referring to the OOME that it instantiated at startup. Note that it did not have to allocate that OOME, it just refers to it.
显然,这些都不是字面上的步骤;它们在实现中会因JVM的不同而不同,但这是高级的想法。
在失败之前,这里发生了大量的工作。JVM将尝试清除SoftReference对象,在使用分代收集器时尝试直接分配到年老代,以及可能的其他事情,比如终结。