我知道每个对象都需要堆内存,堆栈上的每个原语/引用都需要堆栈内存 .
当我尝试在堆上创建一个对象并且没有足够的内存来执行此操作时,JVM会在堆上创建一个java.lang.OutOfMemoryError并将其抛给我 .
所以隐含地说,这意味着JVM在启动时保留了一些内存 .
当这个保留的内存用完时会发生什么(它肯定会用完,下面的讨论),而且JVM上没有足够的内存来创建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
}
==
Why couldn't the JVM reserve sufficient memory?
无论保留多少内存,如果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);
}
}
11 回答
JVM永远不会耗尽内存 . 它预先进行堆栈的内存计算 .
Structure of the JVM, Chapter 3,第3.5.2节规定:
对于 Heap ,第3.5.3节 .
因此,它在分配对象之前预先进行计算 .
会发生什么是JVM尝试为内存中的对象分配内存,称为永久生成区域(或PermSpace) . 如果分配失败(即使在JVM调用垃圾收集器尝试和分配可用空间之后),它也会抛出
OutOfMemoryError
. 即使异常也需要内存空间,因此错误将无限期地抛出 .Further reading.?此外,
OutOfMemoryError
可以出现在不同的JVM structure.中Graham Borland seems to be right:至少我的JVM显然重用了OutOfMemoryErrors . 为了测试这个,我写了一个简单的测试程序:
运行它会产生以下输出:
顺便说一句,我正在运行的JVM(在Ubuntu 10.04上)是这样的:
Edit: 我试图看看如果我使用以下程序强制JVM完全从内存中运行会发生什么:
事实证明,它似乎永远循环 . 但是,奇怪的是,尝试使用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并使用调试器时,堆检查器显示JVM在启动时分配了一个OutOfMemoryError实例 . 换句话说,它在程序有机会开始消耗之前分配对象,更不用说耗尽内存了 .
从JVM规范,第3.5.2节:
每个Java虚拟机都必须保证它会抛出
OutOfMemoryError
. 这意味着,即使没有剩余堆空间,它也必须能够创建OutOfMemoryError
的实例(或者事先创建) .虽然它没有必要保证,但是有足够的内存来捕获它并打印出漂亮的堆栈跟踪...
Addition
您添加了一些代码来表明,如果必须抛出多个
OutOfMemoryError
,JVM可能会耗尽堆空间 . 但是这样的实施会违反上述要求 .不要求
OutOfMemoryError
的抛出实例是唯一的或按需创建的 . JVM可以在启动期间准备一个OutOfMemoryError
的实例,并在堆空间用完时抛出它 - 在正常环境中这是一次 . 换句话说:我们看到的OutOfMemoryError
实例可能是一个单例 .有趣的问题:-) . 虽然其他人对理论方面给出了很好的解释,但我决定尝试一下 . 这是在Oracle JDK 1.6.0_26,Windows 7 64位上 .
Test setup
我写了一个简单的程序来耗尽内存(见下文) .
该程序只是创建一个静态
java.util.List
,并保持填充新的字符串,直到抛出OOM . 然后它捕获它并继续填充无限循环(可怜的JVM ......) .Test result
从输出中可以看出,抛出OOME的前四次,它带有堆栈跟踪 . 之后,随后如果调用
printStackTrace()
,OOME仅打印java.lang.OutOfMemoryError: Java heap space
.显然,如果可以的话,JVM会努力打印堆栈跟踪,但如果内存非常严格,它就会省略跟踪,就像其他答案所暗示的那样 .
同样有趣的是OOME的哈希码 . 请注意,前几个OOME都有不同的哈希值 . 一旦JVM开始省略堆栈跟踪,散列总是相同的 . 这表明JVM将尽可能长时间地使用新的(预分配的?)OOME实例,但是如果推送推进,它将只重用相同的实例,而不是没有任何东西可以抛出 .
Output
注意:我截断了一些堆栈跟踪以使输出更易于阅读("[...]") .
The program
我很确定,JVM将确保它至少有足够的内存来在内存不足之前抛出异常 .
表示试图违反托管内存环境边界的异常由所述环境的运行时处理,在本例中为JVM . JVM是它自己的进程,运行应用程序的IL . 如果程序尝试进行调用以扩展调用堆栈超出限制,或者分配比JVM可以保留的内存更多的内存,则运行时本身将注入异常,这将导致调用堆栈被展开 . 无论程序当前需要多少内存,或者调用堆栈的深度如何,JVM都会在其自己的进程边界内分配足够的内存来创建所述异常并将其注入到代码中 .
您似乎混淆了JVM保留的虚拟内存,其中JVM运行Java程序与主机操作系统的本机内存,其中JVM作为本机进程运行 . 计算机上的JVM在由OS管理的内存中运行,而不是在JVM保留用于运行Java程序的内存中运行 .
进一步阅读:
http://java.sys-con.com/node/1229281
http://publib.boulder.ibm.com/infocenter/javasdk/tools/index.jsp?topic=%2Fcom.ibm.java.doc.igaa%2F_1vg000121410cbe-1195c23a635-7ffa_1001.html
作为最后一点,为了打印堆栈跟踪而尝试 catch (及其后代类)可能不会给你任何有用的信息 . 您想要堆转储 .
为了进一步澄清@Graham Borland的答案,功能上,JVM在启动时执行此操作:
稍后,JVM执行以下Java字节码之一:'new','anewarray'或'multianewarray' . 此指令使JVM在内存不足情况下执行许多步骤:
调用本机函数,比如
allocate()
.allocate()
尝试为某个特定类或数组的新实例分配内存 .该分配请求失败,因此JVM调用另一个本机函数,比如
doGC()
,它尝试进行垃圾回收 .当该函数返回时,
allocate()
再次尝试为该实例分配内存 .如果失败(*),则在allocate()中的JVM只执行
throw OOME;
,指的是它在启动时实例化的OOME . 请注意,它不必分配OOME,它只是引用它 .显然,这些不是文字步骤;它们在实现中会从JVM变为JVM,但这是高级想法 .
(*)在失败之前,这里发生了大量的工作 . JVM将尝试清除SoftReference对象,在使用分代收集器时尝试直接分配到tenured generation,以及可能的其他内容,如finalization .
说JVM将预先分配
OutOfMemoryErrors
的答案确实是正确的 .除了通过激发内存不足情况进行测试之外,我们还可以检查任何JVM的堆(我使用了一个只是睡眠的小程序,使用Oracle的Hotspot JVM从Java 8更新31运行它) .
使用
jmap
,我们看到OutOfMemoryError似乎有9个实例(即使我们有足够的内存):然后我们可以生成堆转储:
并使用Eclipse Memory Analyzer打开它,其中OQL查询显示JVM实际上似乎为所有可能的消息预分配
OutOfMemoryErrors
:Java 8 Hotspot JVM的代码实际上预先分配了这些can be found here,看起来像这样(省略了一些部分):
和this code表明JVM将首先尝试使用其中一个预先分配的错误和空间进行堆栈跟踪,然后回退到没有堆栈跟踪的错误: