【问题标题】:Does the StackMapTable affect the garbage collection behavior?StackMapTable 是否会影响垃圾收集行为?
【发布时间】:2018-02-24 06:30:23
【问题描述】:

我有这样的代码:

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 env:

java版本“1.7.0_79”

Java(TM) SE 运行时环境(内部版本 1.7.0_79-b15)

Java HotSpot(TM) 64 位服务器 VM(内部版本 24.79-b02,混合模式)

案例一:

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

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

data var 被 JVM 收集。

案例二:

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

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

data var 未被收集。

我通过命令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 ]

test2()

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位于)被清除。

有人可以详细解释一下吗?

【问题讨论】:

    标签: java garbage-collection


    【解决方案1】:

    局部变量的范围是编译时的事情。对于字节码,只有最近将哪个值写入局部变量索引才重要。对于垃圾收集器来说,重要的是随后可以访问哪个值。

    但是检测一个值是否随后没有被使用,可能取决于代码的编译/优化级别。在您的简单测试中,代码将始终以解释的方式运行,因此 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[] 变量,就像您原来的@987654334 一样@变体。

    【讨论】:

    • -Xcomp... 我从来没有真正理解过这个标志,它是否能够编译为机器代码而不实际上进行任何内联/转义分析/循环展开或C1C2 JIT 编译器完成的任何其他潜在优化?另一方面,我很久以前就拼命放弃了理解堆栈映射的想法:(
    • test2() 和变体,我想我明白了,因为它们在字节码上是等价的。但是我还是对test1()和变种感到困惑,我猜the paper可以给出答案,但是我还没有理解。
    • @Eugene -Xcomp 在没有分层编译的时候是一个安静的旧选项。它只是强制编译,或者为了更容易理解今天的术语,我们可以说它禁止解释执行。这与强制解释执行的-Xint 正好相反。我认为-Xcomp 根本不会影响C1C2。堆栈映射功能并不难理解,它只是为验证者提供提示,但不会影响其他任何事情。正如在这个答案中所说,这些提示可以帮助 JVM 了解(快速)哪些变量未使用,但未使用(尚未)。
    • @Holger 谢谢,突然间-Xcomp 很有意义,关于这个主题的最高评价问题实际上比这个评论更糟糕......
    猜你喜欢
    • 2010-11-16
    • 1970-01-01
    • 1970-01-01
    • 2012-12-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-03-11
    相关资源
    最近更新 更多