我有这样的代码:
public class TestGC {
private static final int _10MB = 10 * 1024 * 1024; // 10MB
public static void main(String[] args) {
test1();
// test2();
}
public static void test1() {
int i = 1;
if (i > 0) {
byte[] data = new byte[_10MB];
}
System.gc();
}
public static void test2() {
if (true) {
byte[] data = new byte[_10MB];
}
System.gc();
}
}
我使用 jvm 选项 -verbose:gc
运行它,我的 java 环境:
java version "1.7.0_79"
Java(TM) SE Runtime Environment (build 1.7.0_79-b15)
Java HotSpot(TM) 64-Bit Server VM (build 24.79-b02, mixed mode)
案例一:
调用方法 test1()
运行,控制台输出:
[GC 13312K->616K(116736K), 0.0014246 secs]
[Full GC 616K->554K(116736K), 0.0125266 secs]
data
var 由 JVM 收集。
案例2:
调用方法 test2()
运行,控制台输出:
[GC 13312K->10936K(116736K), 0.0092033 secs]
[Full GC 10936K->10788K(116736K), 0.0155626 secs]
data
未收集 var。
我通过命令 javap
为方法生成字节码:
测试1()
public static void test1();
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=1, locals=2, args_size=0
0: iconst_1
1: istore_0
2: iload_0
3: ifle 11
6: ldc #3 // int 10485760
8: newarray byte
10: astore_1
11: invokestatic #4 // Method java/lang/System.gc:()V
14: return
LineNumberTable:
line 11: 0
line 12: 2
line 13: 6
line 15: 11
line 16: 14
LocalVariableTable:
Start Length Slot Name Signature
11 0 1 data [B
2 13 0 i I
StackMapTable: number_of_entries = 1
frame_type = 252 /* append */
offset_delta = 11
locals = [ int ]
测试2()
public static void test2();
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=1, locals=1, args_size=0
0: ldc #3 // int 10485760
2: newarray byte
4: astore_0
5: invokestatic #4 // Method java/lang/System.gc:()V
8: return
LineNumberTable:
line 20: 0
line 22: 5
line 23: 8
LocalVariableTable:
Start Length Slot Name Signature
5 0 0 data [B
我的猜测是:方法test1()
执行到stack map frame时,局部变量被重置,导致slot_1(data
located)被清除。
谁能详细解释一下?
最佳答案
局部变量的范围是编译时的事情。对于字节码,只有哪个值最近写入局部变量索引才重要。对于垃圾收集器来说,重要的是随后可以访问哪个值。
但是检测到一个值没有被后续使用,可能取决于代码的编译/优化级别。在您的简单测试中,代码将始终解释运行,因此 JVM 并不总是检测到创建的数组实际上未被使用。当您使用 -Xcomp
运行测试时,它总是会立即被收集。
您发现的行为取决于字节码中的条件分支,而不是堆栈映射的存在,您可以通过使用 -target 1.5
编译轻松验证(还需要 -source 1.5
),这样编译后的类文件中不存在堆栈映射,而是在相同的运行时环境中运行;行为不会改变。
请注意您的
if (true) {
byte[] data = new byte[_10MB];
}
System.gc();
与
没有区别{
byte[] data = new byte[_10MB];
}
System.gc();
因为您正在对编译时常量进行分支。但是由于您没有覆盖该值,例如通过在范围结束后创建和使用另一个变量,字节码与
byte[] data = new byte[_10MB];
System.gc();
所有这些变体都表现出相同的行为,即不收集堆栈帧仍引用的数组,除非代码已编译。
相比之下,
int i = 1;
if (i > 0) {
byte[] data = new byte[_10MB];
}
System.gc();
带有条件分支,因此在 System.gc()
点,不能使用数组引用,因为代码点可能通过未初始化此变量的路径到达.
同样,数组是用
收集的for(boolean b=true; b; b=!b) {
byte[] data = new byte[_10MB];
}
System.gc();
因为条件分支可以绕过变量初始化,而用
do {
byte[] data = new byte[_10MB];
} while(false);
System.gc();
数组没有被收集,因为变量总是被初始化。
还有,用
public static void test1() {
int i = 1;
if (i > 0) {
byte[] data = new byte[_10MB];
}
else {
byte[] data = new byte[_10MB];
}
System.gc();
}
不收集数组,因为变量总是被初始化,无论代码采用哪个分支。如前所述,仅在解释执行中。
这是此处未使用堆栈映射的标志,正如堆栈映射明确声明的那样,在分支合并点处没有byte[]
变量,就像您原来的 test1()
变体一样。
关于java - StackMapTable 会影响垃圾收集行为吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48960056/