当没有足够的内存来抛出OutOfMemoryError时会发生什么?

Pac*_*ier 206 java language-agnostic

我知道每个对象都需要堆内存,堆栈上的每个原语/引用都需要堆栈内存.

当我尝试在堆上创建一个对象并且没有足够的内存来执行此操作时,JVM会在堆上创建一个java.lang.OutOfMemoryError并将其抛给我.

所以隐含地,这意味着JVM在启动时保留了一些内存.

当这个保留的内存用完时会发生什么(它肯定会用完,下面的讨论),而且JVM上没有足够的内存来创建java.lang.OutOfMemoryError的实例?

它只是挂起来吗?或者他会抛弃我,null因为newOOM的实例没有记忆?

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
}
Run Code Online (Sandbox Code Playgroud)

==

为什么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
                    }
                }
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

或者更简洁:

private void OnOOM(java.lang.OutOfMemoryError e) {
    try {
        e.printStackTrace();
    } catch (java.lang.OutOfMemoryError e2) {
        OnOOM(e2);
    }
}
Run Code Online (Sandbox Code Playgroud)

Buh*_*ndi 145

JVM永远不会耗尽内存.它预先进行堆栈的内存计算.

JVM结构,第3章,第3.5.2节说明:

  • 如果可以动态扩展Java虚拟机堆栈,并且尝试进行扩展但是可以使内存不足以实现扩展,或者如果可用的内存不足以为新线程创建初始Java虚拟机堆栈,则Java虚拟机机器扔了一个OutOfMemoryError.

对于Heap,第3.5.3节.

  • 如果计算需要的堆数超过自动存储管理系统可用的堆,则Java虚拟机会抛出一个OutOfMemoryError.

因此,它在分配对象之前预先进行计算.


会发生什么是JVM尝试为名为Permanent Generation region(或PermSpace)的内存中的对象分配内存.如果分配失败(即使在JVM调用垃圾收集器尝试和分配可用空间之后),它也会抛出一个OutOfMemoryError.即使异常也需要内存空间,因此错误将无限期地抛出.

进一步阅读.?此外,OutOfMemoryError可以发生在不同的JVM结构中.

  • 我的意思是肯定的,但Java虚拟机是否也需要内存来抛出OutOfMemoryError?如果没有内存可以抛出OOM会发生什么? (10认同)
  • @JohnK:我希望核电站不是用Java编程的,就像航天飞机和波音757没有用Java编程一样. (8认同)
  • 但是如果JVM没有返回对同一个OOM实例的引用,那么你不同意它最终会耗尽它的保留内存吗?(如问题中的代码所示) (5认同)
  • 如果虚拟机保留了针对所述极端情况和核电站的OOM异常单例,那么可能会很好. (2认同)

Ilm*_*nen 64

格雷厄姆·博兰德似乎是对的:至少我的 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);
    }
}
Run Code Online (Sandbox Code Playgroud)

运行它会产生以下输出:

$ javac OOMTest.java && java -Xmx10m OOMTest 
Got the same OutOfMemoryError twice: java.lang.OutOfMemoryError: Java heap space
Run Code Online (Sandbox Code Playgroud)

顺便说一句,我正在运行的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)
Run Code Online (Sandbox Code Playgroud)

编辑:我试图看看如果我使用以下程序强制 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);
    }
}
Run Code Online (Sandbox Code Playgroud)

事实证明,它似乎永远循环.但是,奇怪的是,尝试用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

  • @Izkata循环显然是无休止地运行,因为JVM在内存耗尽后不断抛出同一个(第5个左右)OOM.因此它在堆栈上有'n`帧,它最终创建并销毁帧'n + 1`永恒,给出了无休止的运行外观. (10认同)

Gra*_*and 41

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

  • JVM可以返回对OOM异常的单个静态实例的多个引用.因此,即使你的`catch`子句试图使用更多内存,JVM也可以一遍又一遍地抛出相同的OOM实例. (17认同)
  • 这正是OOME从未习惯于包含堆栈跟踪的原因 - 堆栈跟踪占用内存!OOME只是开始尝试在java 6中包含堆栈跟踪(https://blogs.oracle.com/alanb/entry/outofmemoryerror_looks_a_bit_better).我假设如果无法进行堆栈跟踪,则抛出异常而没有堆栈跟踪. (3认同)

ben*_*ado 23

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


And*_*s_D 12

从JVM规范,第3.5.2节:

如果可以动态扩展Java虚拟机堆栈,并且尝试进行扩展但是可以使内存不足以实现扩展,或者如果可用的内存不足以为新线程创建初始Java虚拟机堆栈,则Java虚拟机机器扔了一个OutOfMemoryError.

每个Java虚拟机都必须保证它会抛出一个OutOfMemoryError.这意味着,OutOfMemoryError即使没有剩余堆空间,它也必须能够创建(或预先创建)的实例.

虽然它没有必要保证,但是有足够的内存来捕获它并打印出漂亮的堆栈跟踪......

加成

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

不要求抛出的实例OutOfMemoryError是唯一的或按需创建的.JVM可以OutOfMemoryError在启动期间准备一个实例,并在堆空间用尽时抛出它 - 在正常环境中这是一次.换句话说:OutOfMemoryError我们看到的实例可能是一个单身人士.


sle*_*ske 11

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

测试设置

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

该程序只是创建一个静态java.util.List,并保持填充新的字符串,直到抛出OOM.然后它捕获它并继续填充无限循环(可怜的JVM ......).

测试结果

从输出中可以看出,抛出OOME的前四次,它带有堆栈跟踪.之后,后续的OOME仅java.lang.OutOfMemoryError: Java heap spaceprintStackTrace()调用时打印.

显然,如果可以的话,JVM会努力打印堆栈跟踪,但如果内存非常严格,它就会省略跟踪,就像其他答案所暗示的那样.

同样有趣的是OOME的哈希码.请注意,前几个OOME都有不同的哈希值.一旦JVM开始省略堆栈跟踪,哈希值总是相同的.这表明JVM将尽可能长时间地使用新的(预分配的?)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...
[...]
Run Code Online (Sandbox Code Playgroud)

该程序

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

    }
}
Run Code Online (Sandbox Code Playgroud)


Osc*_*mez 6

我很确定,JVM将确保它至少有足够的内存来在内存耗尽之前抛出异常.