55

最大メモリが 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];
}
4

2 に答える 2

35

全体像を把握するために、次のコードを使用して実行することを検討して-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;
}

ウォーミングアップをするか飛ばすかで吹くか吹かないかで決まる。これはnull、解釈されたコードがそうでないのに対し、JITted コードは適切に 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

どちらの場合も明示的な ing out はありませんが、たとえばの例とは対照的に、たとえばの例では同じメモリ位置が実際に再利用さ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 のコードにも当てはまりますが、今度は無害な長さ 0 の配列への参照で場所を上書きし、別の場所を使用して巨大な配列への参照を格納します。

要約すると...

Java 言語仕様では、ガベージ オブジェクトを収集する必要があるとは指定されておらず、JVM 仕様では、メソッドの完了時にローカル変数を含む「フレーム」が全体として破棄されるとのみ述べられています。したがって、私たちが目撃したすべての行動は本によるものです。オブジェクトの非表示状態 ( keppil によってリンクされているドキュメントで言及されています)一部の実装や特定の状況で何が起こっているかを説明する方法にすぎませんが、いかなる種類の標準的な動作でもありません。

于 2012-11-23T15:05:45.097 に答える
26

これは、ブラケットの後の whileはスコープ内になく、メソッドが戻るまで invisiblea1と呼ばれる状態にあるためです。

最近のほとんどの JVM は、スコープを離れるとすぐに変数a1を設定しませんnull(実際には、内側の括弧があるかどうかは、生成されたバイト コードを変更することさえありません)。案件。したがって、a1メソッドが戻るまでガベージ コレクションを行うことはできません。

これは、次の行を追加することで確認できます

a1 = null;

これにより、プログラムが正常に実行されます。

目に見えないという用語とその説明は、次の古い論文から引用されていますhttp://192.9.162.55/docs/books/performance/1st_edition/html/JPAppGC.fm.html

于 2012-11-23T14:50:04.057 に答える