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

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?

Holger
  • 285,553
  • 42
  • 434
  • 765

1 Answers1

3

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.

Holger
  • 285,553
  • 42
  • 434
  • 765
  • `-Xcomp`... I never actually quite understood this flag, is it enabling compilation to machine code *without* actually doing any inlining/escape analysis/loop unrolling or *any* other potential optimization done by `C1` or `C2` JIT compilers? On the other hand I desperately gave up on the idea of understanding stack maps a lot of time ago :( – Eugene Feb 27 '18 at 21:00
  • `test2()` and the variant, I think i have got it, as they are equivalent on bytecode. But I still confused with `test1()` and the variant, I guess [the paper](http://citeseer.ist.psu.edu/viewdoc/summary?doi=10.1.1.47.6924) can give the answer, But I have not understood yet. – Spiro Huang Feb 28 '18 at 02:04
  • 1
    @Eugene `-Xcomp` is quiet an old option from a time without tiered compilation. It simply forces compilation or, to make it easier to understand for today’s terms, we could say it forbids interpreted execution. It’s the opposite of `-Xint` which forces interpreted execution. I don’t think that `-Xcomp` affects the `C1` and `C2` at all. The stack maps feature is not so hard to understand, it just provides hints to the verifier, but does not affect anything else. As said in this answer, these hints *could* help the JVM understanding (quickly) which variables are unused, but are not used (yet). – Holger Feb 28 '18 at 07:15
  • @Holger thank you, suddenly `-Xcomp` makes a lot of sense, the top rated question about this subject here on SO is actually worse than this comment... – Eugene Feb 28 '18 at 08:21