簡體   English   中英

Java OutOfMemoryError奇怪的行為

[英]Java OutOfMemoryError strange behaviour

假設我們的最大內存為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代碼在var中正確地為null ,而解釋的代碼則沒有。 這兩種行為在Java語言規范下都是可以接受的,這意味着你受JVM的支配。

在OS X上使用Java HotSpot(TM) 64-Bit Server VM (build 23.3-b01, mixed mode)進行測試。

字節碼分析

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        

用於a1的位置2重用於a2 對於OP的代碼也是如此,但現在我們用一個無害的零長度數組覆蓋該位置,並使用另一個位置來存儲對我們的巨大數組的引用。

把它們加起來...

Java語言規范沒有指定必須收集任何垃圾對象,並且JVM規范僅表示在方法完成時將具有局部變量的“框架”作為一個整體銷毀。 因此,我們目睹的所有行為都在書中。 對象的不可見狀態(在由keppil鏈接的文檔中提到)只是描述在某些實現中和在某些情況下發生的事情的一種方式,但絕不是任何類型的規范行為。

這是因為雖然a1不在括號之后的范圍內,但是在方法返回之前它處於一個名為invisible的狀態。

大多數現代JVM在離開作用域時都不會將變量a1設置為null (實際上,內部括號是否存在甚至不會更改生成的字節代碼),因為它非常無效,並且通常不會沒關系 因此,在方法返回之前,不能對a1進行垃圾回收。

您可以通過添加行來檢查

a1 = null;

在括號內,這使程序運行正常。

隱形術語和解釋來自這篇舊論文: 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