Does the StackMapTable affect the garbage collection behavior?

浪尽此生 提交于 2021-02-07 20:18:53

问题


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();
    }
}

I run it with jvm option -verbose:gc, My java env:

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)

CASE-1:

Run with method test1() invoked, console output:

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

data var is collected by JVM.

CASE-2:

Run with method test2() invoked, console output:

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

data var is not collected.

I generate bytecode for methods by command 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

My guess is: When method test1() execute to stack map frame, the local variables is reset and lead to slot_1(data located) is cleared.

Someone can give a detail explain?


回答1:


The scope of local variables is a compile-time thing. For the byte code, it only matters, which value was most recently written to a local variable index. For the garbage collector, it only matters, which value may subsequently get accessed.

But detecting that a value is not subsequently used, may depend on the compilation/optimization level of the code. In your simple test, the code will always run interpreted, so the JVM does not always detect that the created array is actually unused. When you run you test with -Xcomp, it will always get collected immediately.

The behavior you have discovered depends on the conditional branches found in the byte code, but not on the presence of stack maps, which you can easily verify by compiling with -target 1.5 (also needs -source 1.5), so that no stack maps are present in the compiled class file, but run on the same runtime environment; the behavior doesn’t change.

Note that your

if (true) {
    byte[] data = new byte[_10MB];
}
System.gc();

is not different to

{
    byte[] data = new byte[_10MB];
}
System.gc();

as you are branching over a compile-time constant. But since you are not overwriting the value, e.g. by creating and using another variable after the end of the scope, the byte code doesn’t differ from

byte[] data = new byte[_10MB];
System.gc();

All these variants exhibit the same behavior of not collecting the array still referenced by the stack frame, unless the code got compiled.

In contrast,

int i = 1;
if (i > 0) {
    byte[] data = new byte[_10MB];
}
System.gc();

bears a conditional branch, so at the System.gc() point, the array reference can’t be used, as the code point might get reached through a path where this variable is not initialized.

Likewise, the array is collected with

for(boolean b=true; b; b=!b) {
    byte[] data = new byte[_10MB];
}
System.gc();

as the conditional branch may bypass the variable initialization, while with

do {
    byte[] data = new byte[_10MB];
} while(false);
System.gc();

the array is not collected, as the variable is always initialized.

Also, with

public static void test1() {
    int i = 1;
    if (i > 0) {
        byte[] data = new byte[_10MB];
    }
    else {
        byte[] data = new byte[_10MB];
    }
    System.gc();
}

the array is not collected, as the variables is always initialized, regardless of which branch the code takes. As said, only in interpreted execution.

This is a sign that the stack map is not used here, as the stack map clearly declares, that there is no byte[] variable at the branch merge point, like in your original test1() variant.



来源:https://stackoverflow.com/questions/48960056/does-the-stackmaptable-affect-the-garbage-collection-behavior

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!