StackMapTable会影响垃圾回收行为吗?

3

I have code like this:

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版本号为"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)

CASE-1:

通过调用test1()方法运行,控制台输出:

[GC 13312K->616K(116736K), 0.0014246 secs]
[Full GC 616K->554K(116736K), 0.0125266 secs]

data 变量由 JVM 收集。

CASE-2:

运行时调用 test2() 方法,控制台输出:

[GC 13312K->10936K(116736K), 0.0092033 secs]
[Full GC 10936K->10788K(116736K), 0.0155626 secs]

data变量未被收集。

我使用javap命令为方法生成字节码:

test1()

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()执行到堆栈映射帧时,局部变量被重置,导致slot_1(保存data的位置)被清除。
有人能详细解释一下吗?
1个回答

3
本地变量的作用域是编译时的事情。对于字节码而言,最近写入本地变量索引的值才是重要的。对于垃圾收集器而言,只有可能随后访问的值才是重要的。
但是,检测到一个值不再被使用,可能取决于代码的编译/优化级别。在您的简单测试中,代码将始终运行解释执行,因此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()变体中一样。


“-Xcomp”这个标志,我从来没有真正理解过它的含义。它是启用将编译转换为机器代码,而不进行任何内联/逃逸分析/循环展开或C1/C2 JIT编译器可能执行的*任何其他潜在优化吗?另一方面,我已经放弃了理解堆栈映射的想法很长时间了:( - Eugene
test2()和它的变体我想已经明白了,因为它们在字节码上是等价的。但是我仍然对test1()及其变体感到困惑,我猜这篇论文可以给出答案,但我还没有理解。 - Spiro Huang
1
@Eugene -Xcomp 是一个比较古老的选项,它是在分层编译出现之前的时期使用的。它只是强制编译,或者更容易理解为禁止解释执行。这与 -Xint 相反,后者强制解释执行。我认为 -Xcomp 不会对 C1C2 产生任何影响。堆栈映射功能并不难理解,它只是为验证器提供提示,但不会影响任何其他内容。正如本答案所述,这些提示 可能 有助于 JVM(快速)了解哪些变量未被使用,但目前还没有使用。 - Holger
@Holger 谢谢,突然之间 -Xcomp 就有了很多意义,在这里关于这个主题的最高评价问题实际上比这个评论更糟糕... - Eugene

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接