Java OutOfMemoryError奇怪的行为
假设我们有一个256M的最大内存,为什么这个代码工作:
public static void main(String... args) { for (int i = 0; i < 2; i++) { byte[] a1 = new byte[150000000]; } byte[] a2 = new byte[150000000]; }
但是这个扔OOME?
public static void main(String... args) { //for (int i = 0; i < 2; i++) { byte[] a1 = new byte[150000000]; } byte[] a2 = new byte[150000000]; }
为了保持-Xmx64m
,请考虑使用-Xmx64m
运行此代码:
static long sum; public static void main(String[] args) { System.out.println("Warming up..."); for (int i = 0; i < 100_000; i++) test(1); System.out.println("Main call"); test(5_500_000); System.out.println("Sum: " + sum); } static void test(int size) { // for (int i = 0; i < 1; i++) { long[] a2 = new long[size]; sum += a2.length; } long[] a1 = new long[size]; sum += a1.length; }
根据你是否进行热身或跳过,它会吹或不吹。 这是因为JITted代码正确地将null
置于var之外,而解释的代码不会。 在Java语言规范下,这两种行为都是可以接受的,这意味着你受此JVM的摆布。
在OS X上使用Java HotSpot(TM) 64-Bit Server VM (build 23.3-b01, mixed mode)
进行testing。
字节码分析
用for
循环查看字节码(简单的代码,没有sum
variables):
static void test(int); Code: 0: iconst_0 1: istore_1 2: goto 12 5: iload_0 6: newarray long 8: astore_2 9: iinc 1, 1 12: iload_1 13: iconst_1 14: if_icmplt 5 17: iload_0 18: newarray long 20: astore_1 21: return
没有:
static void test(int); Code: 0: iload_0 1: newarray long 3: astore_1 4: iload_0 5: newarray long 7: astore_1 8: return
在这两种情况下都没有明确的null
,但是请注意,在这种情况下,例如,相同的内存位置实际上被重用,与此相反。 如果有的话,这将导致与观察到的行为相反的期望。
扭曲…
根据我们从字节码中学到的东西,试着运行这个:
public static void main(String[] args) { { long[] a1 = new long[5_000_000]; } long[] a2 = new long[0]; long[] a3 = new long[5_000_000]; }
没有OOME抛出 。 注释掉a2
的声明,它就回来了。 我们分配更多 ,但占用更less ? 看看字节码:
public static void main(java.lang.String[]); Code: 0: ldc #16 // int 5000000 2: istore_1 3: ldc #16 // int 5000000 5: newarray long 7: astore_2 8: iconst_0 9: newarray long 11: astore_2 12: ldc #16 // int 5000000 14: newarray long 16: astore_3 17: return
用于a1
的位置2被重新用于a2
。 OP的代码也是如此,但是现在我们用一个无关的零长度数组来引用位置,并使用另一个位置来存储对我们巨大数组的引用。
把它们加起来…
Java语言规范并没有指定任何垃圾对象必须被收集,而JVM规范只是说在方法完成时,局部variables的“框架”被作为一个整体销毁。 所以我们目睹的所有行为都是靠书本来的。 一个对象的不可见状态(在keppil中链接的文档中提到)只是一种描述在某些实现中,在某些情况下会发生什么的方法,但绝不是任何一种规范的行为。
这是因为虽然a1
不在括号之后的范围内,但在方法返回之前,它处于不可见状态。
大多数现代的JVM一旦离开作用域就不会将variablesa1
设置为null
(实际上,内部括号是否存在甚至不会改变生成的字节码),因为它非常无效,并且通常不会没关系。 因此,在方法返回之前, a1
不能被垃圾回收。
您可以通过添加该行来检查
a1 = null;
在括号内,这使程序运行良好。
这个术语是隐形的 ,解释来源于这篇旧论文: http://192.9.162.55/docs/books/performance/1st_edition/html/JPAppGC.fm.html
: http://192.9.162.55/docs/books/performance/1st_edition/html/JPAppGC.fm.html
。