简体   繁体   English

Java OutOfMemoryError奇怪的行为

[英]Java OutOfMemoryError strange behaviour

Assuming we have a max memory of 256M, why does this code work: 假设我们的最大内存为256M,为什么这段代码有效:

public static void main(String... args) {
  for (int i = 0; i < 2; i++)
  {
      byte[] a1 = new byte[150000000];
  }
  byte[] a2 = new byte[150000000];
}

but this one throw an OOME? 但是这个扔了一个OOME?

public static void main(String... args) {
  //for (int i = 0; i < 2; i++)
  {
      byte[] a1 = new byte[150000000];
  }
  byte[] a2 = new byte[150000000];
}

To keep things in perspective, consider running this code with -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;
}

Depending on whether you do the warmup or skip it, it will blow or not blow. 根据您是进行预热还是跳过它,它会吹还是不吹。 This is because the JITted code properly null s out the var, whereas the interpreted code doesn't. 这是因为JITted代码在var中正确地为null ,而解释的代码则没有。 Both behaviors are acceptable under the Java Language Specification, which means that you are at the mercy of the JVM with this. 这两种行为在Java语言规范下都是可以接受的,这意味着你受JVM的支配。

Tested with Java HotSpot(TM) 64-Bit Server VM (build 23.3-b01, mixed mode) on OS X. 在OS X上使用Java HotSpot(TM) 64-Bit Server VM (build 23.3-b01, mixed mode)进行测试。

Bytecode analysis 字节码分析

Look at the bytecode with the for loop (simple code, without the sum variable): for循环查看字节码(简单代码,不带sum变量):

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

and without: 没有:

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

No explicit null ing out in either case, but note that in the no-for example the same memory location is actually reused, in contrast with the for example. 没有明确的null荷兰国际集团出在这两种情况下,但要注意的是,在没有例如相同的内存位置实际上是重复使用,相比之下同例如 This would, if anything, lead to the expectation opposite to the observed behavior. 如果有的话,这将导致与观察到的行为相反的期望。

A twist... 扭曲......

Based on what we learned from the bytecode, try running this: 根据我们从字节码学到的知识,尝试运行:

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

No OOME thrown . 没有OOME被抛出 Comment out the declaration of a2 , and it is back. 注释掉a2的声明,它又回来了。 We allocate more , but occupy less ? 我们分配更多 ,但占用更少 Look at the bytecode: 看一下字节码:

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        

The location 2, used for a1 , is reused for a2 . 用于a1的位置2重用于a2 The same is true for OP's code, but now we overwrite the location with a reference to an innocuous zero-length array, and use another location to store the reference to our huge array. 对于OP的代码也是如此,但现在我们用一个无害的零长度数组覆盖该位置,并使用另一个位置来存储对我们的巨大数组的引用。

To sum it up... 把它们加起来...

Java Language Specification does not specify that any garbage object must be collected and the JVM spec only says that the "frame" with local variables is destroyed as a whole upon method completion. Java语言规范没有指定必须收集任何垃圾对象,并且JVM规范仅表示在方法完成时将具有局部变量的“框架”作为一个整体销毁。 Therefore all behaviors that we have witnessed are by the book. 因此,我们目睹的所有行为都在书中。 The invisible state of an object (mentioned in the document linked to by keppil ) is just a way to describe what happens to go on in some implementations and under some circumstances, but is in no way any kind of canonical behavior. 对象的不可见状态(在由keppil链接的文档中提到)只是描述在某些实现中和在某些情况下发生的事情的一种方式,但绝不是任何类型的规范行为。

This is because while a1 isn't in scope after the brackets, it is in a state called invisible until the method returns. 这是因为虽然a1不在括号之后的范围内,但是在方法返回之前它处于一个名为invisible的状态。

Most modern JVMs don't set the variable a1 to null as soon as it leaves the scope (actually, whether the inner brackets are there or not doesn't even change the generated byte code), because it is very ineffective, and usually doesn't matter. 大多数现代JVM在离开作用域时都不会将变量a1设置为null (实际上,内部括号是否存在甚至不会更改生成的字节代码),因为它非常无效,并且通常不会没关系 Therefore, a1 can't be garbage collected until the method returns. 因此,在方法返回之前,不能对a1进行垃圾回收。

You can check this by adding the line 您可以通过添加行来检查

a1 = null;

inside the brackets, which makes the program run fine. 在括号内,这使程序运行正常。

The term invisible and the explanation is taken from this old paper: 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.htmlhttp://192.9.162.55/docs/books/performance/1st_edition/html/JPAppGC.fm.html

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM