当内存不足时抛出OutOfMemoryError会发生什么?

我知道每个对象都需要堆内存,堆栈中的每个基元/引用都需要堆栈内存。

当我尝试在堆上创build一个对象并且内存不足时,JVM会在堆上创build一个java.lang.OutOfMemoryError并将其抛给我。

所以隐含的意思是,这意味着在启动时JVM保留了一些内存。

当这个保留的内存用完时(这肯定会被用完,请阅读下面的讨论),并且JVM没有足够的内存来创build一个java.lang.OutOfMemoryError的实例?

它挂了吗? 还是他会扔我一个null因为没有记忆new的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不能保留足够的内存?

无论保留多less内存,如果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); } } 

JVM从来没有真正用完内存。 它预先执行堆栈的内存计算。

JVM的结构,第3章第3.5.2节指出:

  • 如果可以dynamic扩展Java虚拟机堆栈,并尝试扩展,但是没有足够的内存可用于扩展,或者如果没有足够的内存可用于为新线程创build初始Java虚拟机堆栈,则Java虚拟机机器抛出一个OutOfMemoryError

对于 ,3.5.3节。

  • 如果计算需要比自动存储pipe理系统可用的更多的堆,则Java虚拟机会抛出OutOfMemoryError

所以,在进行对象分配之前先进行一次计算。


会发生什么是JVM尝试为永久生成区域(或PermSpace)内存中的对象分配内存。 如果分配失败(甚至在JVM调用垃圾收集器尝试分配可用空间之后),则会抛出OutOfMemoryError 。 即使是exception也需要一个内存空间,所以错误将被无限期地抛出。

进一步阅读。 ? 而且, OutOfMemoryError可能发生在不同的JVM结构中。

Graham Borland似乎是对的 :至less我的 JVM显然重用了OutOfMemoryErrors。 为了testing这个,我写了一个简单的testing程序:

 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); } } 

事实certificate,它似乎永远循环。 但是,奇怪的是,试图用Ctrl + C来终止程序是行不通的,但是只给出如下的信息:

Java HotSpot(TM) 64-Bit Server VM warning: Exception java.lang.OutOfMemoryError occurred dispatching signal SIGINT to handler- the VM may need to be forcibly terminated

大多数运行时环境将在启动时预先分配,或以其他方式预留足够的内存来处理内存不足情况。 我想大多数理智的JVM实现会这样做。

上次我使用Java并使用debugging器时,堆检查器显示JVM在启动时分配了一个OutOfMemoryError实例。 换句话说,它在你的程序有机会开始消费之前分配对象,更不用说用完了内存。

从JVM规范的第3.5.2章:

如果可以dynamic扩展Java虚拟机堆栈,并尝试扩展,但是没有足够的内存可用于扩展,或者如果没有足够的内存可用于为新线程创build初始Java虚拟机堆栈, 则Java虚拟机机器抛出一个OutOfMemoryError

每个Java虚拟机必须保证它会抛出一个OutOfMemoryError 。 这意味着,即使没有剩余空间,它也必须能够创buildOutOfMemoryError的实例(或者提前创build)。

虽然它不必保证,但有足够的内存来捕捉它,并打印一个不错的堆栈跟踪…

加成

您添加了一些代码来显示,如果必须抛出多个OutOfMemoryError ,JVM可能会耗尽堆空间。 但这样的实施将违反上述要求。

没有要求OutOfMemoryError的引发实例是唯一的或按需创build的。 JVM可以在启动过程中准确地准备一个OutOfMemoryError实例,并在堆空间用完时抛出 – 这是正常环境中的一次。 换句话说:我们看到的OutOfMemoryError的实例可能是一个单例。

有趣的问题:-)。 虽然其他人对理论方面作了很好的解释,但我决定尝试一下。 这是在Oracle JDK 1.6.0_26,Windows 7 64位上。

testing设置

我写了一个简单的程序来耗尽内存(见下文)。

该程序只是创build一个静态java.util.List ,并不断填充新鲜的string,直到OOM抛出。 然后抓住它并继续无休止的循环(可怜的JVM …)。

testing结果

从输出中可以看到,前四次OOME被抛出,它带有一个堆栈跟踪。 之后,后续的OOME只打印java.lang.OutOfMemoryError: Java heap space如果printStackTrace()被调用, java.lang.OutOfMemoryError: Java heap space

显然,JVM尽可能地打印堆栈跟踪,但是如果内存非常紧,就像其他答案所暗示的那样,它只是省略了跟踪。

另外有趣的是OOME的哈希码。 请注意,前几个OOME都有不同的哈希。 一旦JVM开始省略堆栈跟踪,散列总是相同的。 这表明JVM将尽可能使用全新的(预分配的)OOME实例,但是如果push被推入,它将只是重用相同的实例,而不是没有任何东西被抛出。

产量

注:我截断了一些堆栈跟踪,使输出更容易阅读(“[…]”)。

 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); } } } } 

我非常肯定,JVM将会确保它至less有足够的内存在内存不足之前抛出exception。

表示尝试违反托pipe内存环境边界的exception由所述环境的运行时处理,在这种情况下是JVM。 JVM是它自己的进程,它正在运行你的应用程序的IL。 如果程序尝试调用扩展调用堆栈超出限制,或分配的内存超过JVM可以保留的内存,运行时本身将注入一个exception,这将导致调用堆栈被解除。 无论程序当前需要多less内存,或者调用堆栈有多深,JVM都会在自己的进程范围内分配足够的内存来创buildexception并将其注入代码中。

您似乎将JVM运行Java程序的JVM保留的虚拟内存与JVM作为本机进程运行的主机操作系统本机内存混淆了。 机器上的JVM运行在由OSpipe理的内存中,而不是JVM保留用于运行Java程序的内存中。

进一步阅读:

作为最后一点,试图捕获一个java.lang.Error (及其后代类),以便打印堆栈跟踪可能不会给你任何有用的信息。 你想要一个堆转储。

为了进一步澄清@Graham Borland的回答,从function上来说,JVM在启动时会这样做:

 private static final OutOfMemoryError OOME = new OutOfMemoryError(); 

稍后,JVM执行以下Java字节码之一:“new”,“newarray”或“multianewarray”。 该指令会导致JVM在内存不足情况下执行多个步骤:

  1. 调用一个本地函数,比如allocate()allocate()尝试为某个特定类或数组的新实例分配内存。
  2. 这个分配请求失败,所以JVM调用另一个本地函数doGC() ,它试图进行垃圾回收。
  3. 当该函数返回时, allocate()再次尝试为实例分配内存。
  4. 如果失败(*),那么allocate()中的JVM只是throw OOME; ,指的是在启动时实例化的OOME。 请注意,它不必分配该OOME,它只是指它。

显然,这不是文字上的步骤, 在实现中它们将从JVM到JVM有所不同,但这是高层次的想法。

(*)在失败之前,大量的工作在这里发生。 JVM将尝试清除SoftReference对象,在使用分代收集器时尝试直接分配到生成的代中,以及可能的其他事情,如最终化。

JVM将预先分配OutOfMemoryErrors的答案确实是正确的。
除了通过引发内存不足的情况来testing,我们可以检查任何JVM的堆(我使用一个小程序,只是hibernate,使用Oracle的Hotspot JVM从Java 8更新31运行它)。

使用jmap我们看到似乎有9个OutOfMemoryError实例(即使我们有足够的内存):

 > jmap -histo 12103 |  grep OutOfMemoryError
  71:9 288 java.lang.OutOfMemoryError
 170:1 32 [Ljava.lang.OutOfMemoryError;

然后我们可以生成一个堆转储:

 > jmap -dump:format = b,file = heap.hprof 12315

并使用Eclipse Memory Analyzer打开它,其中一个OQL查询显示JVM实际上似乎为所有可能的消息预先分配OutOfMemoryErrors

在这里输入图像说明

Java 8 Hotspot JVM的代码实际上可以在这里find ,看起来像这样(省略了一些部分):

 ... // Setup preallocated OutOfMemoryError errors k = SystemDictionary::resolve_or_fail(vmSymbols::java_lang_OutOfMemoryError(), true, CHECK_false); k_h = instanceKlassHandle(THREAD, k); Universe::_out_of_memory_error_java_heap = k_h->allocate_instance(CHECK_false); Universe::_out_of_memory_error_metaspace = k_h->allocate_instance(CHECK_false); Universe::_out_of_memory_error_class_metaspace = k_h->allocate_instance(CHECK_false); Universe::_out_of_memory_error_array_size = k_h->allocate_instance(CHECK_false); Universe::_out_of_memory_error_gc_overhead_limit = k_h->allocate_instance(CHECK_false); ... if (!DumpSharedSpaces) { // These are the only Java fields that are currently set during shared space dumping. // We prefer to not handle this generally, so we always reinitialize these detail messages. Handle msg = java_lang_String::create_from_str("Java heap space", CHECK_false); java_lang_Throwable::set_message(Universe::_out_of_memory_error_java_heap, msg()); msg = java_lang_String::create_from_str("Metaspace", CHECK_false); java_lang_Throwable::set_message(Universe::_out_of_memory_error_metaspace, msg()); msg = java_lang_String::create_from_str("Compressed class space", CHECK_false); java_lang_Throwable::set_message(Universe::_out_of_memory_error_class_metaspace, msg()); msg = java_lang_String::create_from_str("Requested array size exceeds VM limit", CHECK_false); java_lang_Throwable::set_message(Universe::_out_of_memory_error_array_size, msg()); msg = java_lang_String::create_from_str("GC overhead limit exceeded", CHECK_false); java_lang_Throwable::set_message(Universe::_out_of_memory_error_gc_overhead_limit, msg()); msg = java_lang_String::create_from_str("/ by zero", CHECK_false); java_lang_Throwable::set_message(Universe::_arithmetic_exception_instance, msg()); // Setup the array of errors that have preallocated backtrace k = Universe::_out_of_memory_error_java_heap->klass(); assert(k->name() == vmSymbols::java_lang_OutOfMemoryError(), "should be out of memory error"); k_h = instanceKlassHandle(THREAD, k); int len = (StackTraceInThrowable) ? (int)PreallocatedOutOfMemoryErrorCount : 0; Universe::_preallocated_out_of_memory_error_array = oopFactory::new_objArray(k_h(), len, CHECK_false); for (int i=0; i<len; i++) { oop err = k_h->allocate_instance(CHECK_false); Handle err_h = Handle(THREAD, err); java_lang_Throwable::allocate_backtrace(err_h, CHECK_false); Universe::preallocated_out_of_memory_errors()->obj_at_put(i, err_h()); } Universe::_preallocated_out_of_memory_error_avail_count = (jint)len; } ... 

这段代码显示JVM将首先尝试使用一个预先分配的错误和一个堆栈跟踪的空间,然后回退到没有堆栈跟踪的一个:

 oop Universe::gen_out_of_memory_error(oop default_err) { // generate an out of memory error: // - if there is a preallocated error with backtrace available then return it wth // a filled in stack trace. // - if there are no preallocated errors with backtrace available then return // an error without backtrace. int next; if (_preallocated_out_of_memory_error_avail_count > 0) { next = (int)Atomic::add(-1, &_preallocated_out_of_memory_error_avail_count); assert(next < (int)PreallocatedOutOfMemoryErrorCount, "avail count is corrupt"); } else { next = -1; } if (next < 0) { // all preallocated errors have been used. // return default return default_err; } else { // get the error object at the slot and set set it to NULL so that the // array isn't keeping it alive anymore. oop exc = preallocated_out_of_memory_errors()->obj_at(next); assert(exc != NULL, "slot has been used already"); preallocated_out_of_memory_errors()->obj_at_put(next, NULL); // use the message from the default error oop msg = java_lang_Throwable::message(default_err); assert(msg != NULL, "no message"); java_lang_Throwable::set_message(exc, msg); // populate the stack trace and return it. java_lang_Throwable::fill_in_stack_trace_of_preallocated_backtrace(exc); return exc; } }