Java OutOfMemoryError strange 的行为

内容来源于 Stack Overflow,并遵循CC BY-SA 3.0许可协议进行翻译与使用

  • 回答 (2)
  • 关注 (0)
  • 查看 (20)

假设我们有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

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来控制它。

Java HotSpot(TM) 64-Bit Server VM (build 23.3-b01, mixed mode)在OS X上 测试。

字节码分析

查看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

没有:

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,它就回来了。我们分配更多,但占用更少?看看字节码:

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        

用于的位置2 a1被重用a2。OP的代码也是如此,但是现在我们用一个无害的零长度数组引用覆盖位置,并使用另一个位置来存储对我们巨大数组的引用。

Java语言规范没有规定必须收集任何垃圾对象,并且JVM规范只是说在方法完成时将带有局部变量的“框架”作为一个整体销毁。因此,我们目睹的所有行为都是通过这本书。一个对象的不可见状态(在由keppil链接的文档中提到)只是一种描述在某些实现中以及在某些情况下会发生什么的方式,但决不是任何一种规范行为。

用户回答回答于

这是因为虽然a1括号之后的范围不在范围内,但在方法返回之前它处于不可见状态。

大多数现代JVM不要将变量设置a1,以null尽快离开范围(实际上,内侧支架是否存在与否甚至不更改生成字节码),因为它是非常低效,而且通常不物。因此,a1直到方法返回时才能收集垃圾。

您可以通过添加该行来检查

a1 = null;

在括号内,这使程序运行良好。

这个术语是不可见的,解释来源于这篇旧论文:http://192.9.162.55/docs/books/performance/1st_edition/html/JPAppGC.fm.html

扫码关注云+社区