java - StackMapTable 会影响垃圾收集行为吗?

标签 java garbage-collection

我有这样的代码:

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/

相关文章:

Java编译文件目录问题

go - gc如何处理slice内存回收

java - RecyclerView 从另一个类中删除一个项目

java - Camel - CamelContext - RabbitMQ 组件 - ExchangeName 的值应该是多少?

java - 使用 Java 声音 API 修剪 WAV 文件的开头和结尾

java - 在 Java 8 lambda 中使用时,EJB 调用缺少调用者主体

.net - 如何分析 .net 垃圾收集器?

java -/usr/bin/time 是否包括 java 程序的程序后垃圾收集?

java - JIT 能够优化内存分配吗?

c# - 终结器在其对象仍在使用时启动