全体像を把握するために、次のコードを使用して実行することを検討して-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 によってリンクされているドキュメントで言及されています)は、一部の実装や特定の状況で何が起こっているかを説明する方法にすぎませんが、いかなる種類の標準的な動作でもありません。