假设我们的最大内存为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];
}
答案 0 :(得分: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;
}
根据您是进行预热还是跳过它,它会吹或不吹。这是因为JITted代码正确地null
出了var,而解释的代码却没有。这两种行为在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
,但请注意,在 no-for 示例中,实际上重用了相同的内存位置,而 for 例子。如果有的话,这将导致与观察到的行为相反的期望。
根据我们从字节码学到的内容,尝试运行:
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 的文档中提到)只是描述在某些实现中和某些情况下会发生什么的一种方式,但是绝不是任何一种规范行为。
答案 1 :(得分:26)
这是因为虽然a1
不在括号之后的范围内,但它处于一种名为不可见的状态,直到该方法返回。
大多数现代JVM在离开作用域时都不会将变量a1
设置为null
(实际上,内部括号是否存在甚至不会更改生成的字节代码),因为它非常无效,而且通常无关紧要。因此,在方法返回之前,不能对a1
进行垃圾回收。
您可以通过添加行
来检查a1 = null;
括号内的使程序运行正常。
术语不可见,解释来自这篇旧论文:http://192.9.162.55/docs/books/performance/1st_edition/html/JPAppGC.fm.html
。