GC behavior when assigning null to reference variable

I was trying to understand the behavior of GC and I found something that interests me which I am unable to understand.

Please see the code and output:

public class GCTest {
    private static int i=0;

    @Override
    protected void finalize() throws Throwable {
        i++; //counting garbage collected objects
    }

    public static void main(String[] args) {        
        GCTest holdLastObject; //If I assign null here then no of eligible objects are 9 otherwise 10.

        for (int i = 0; i < 10; i++) {            
             holdLastObject=new GCTest();             
        }

        System.gc(); //requesting GC

        //sleeping for a while to run after GC.
        try {
            Thread.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // final output 
        System.out.println("`Total no of object garbage collected=`"+i);          
    }
}

In above example if I assign holdLastObject to null then I get Total no of object garbage collected=9. If I do not, I get 10.

Can someone explain it? I am unable to find the correct reason.

Jon Skeet
people
quotationmark

I suspect it's due to definite assignment.

If you assign a value to holdLastObject before the loop, it's definitely assigned for the whole method (from the point of declaration onwards) - so even though you don't access it after the loop, the GC understands that you could have written code that accessses it, so it doesn't finalize the last instance.

As you don't assign a value to the variable before the loop, it's not definitely assigned except within the loop - so I suspect the GC treats it as if it were declared in the loop - it knows that no code after the loop could read from the variable (because it's not definitely assigned) and so it knows it can finalize and collect the last instance.

Just to clarify what I mean by this, if you add:

System.out.println(holdLastObject);

just before the System.gc() line, you'll find it won't compile in your first case (without the assignment).

I suspect this is a VM detail though - I'd hope that if the GC could prove that no code was actually going to read from the local variable, it would be legal for it to collect the final instance anyway (even if it isn't implemented that way at the moment).

EDIT: Contrary to TheLostMind's answer, I believe the compiler gives this information to the JVM. Using javap -verbose GCTest I found this without the assignment:

  StackMapTable: number_of_entries = 4
    frame_type = 253 /* append */
      offset_delta = 2
      locals = [ top, int ]
    frame_type = 249 /* chop */
      offset_delta = 19
    frame_type = 75 /* same_locals_1_stack_item */
      stack = [ class java/lang/InterruptedException ]
    frame_type = 4 /* same */

and this with the assigment:

  StackMapTable: number_of_entries = 4
    frame_type = 253 /* append */
      offset_delta = 4
      locals = [ class GCTest, int ]
    frame_type = 250 /* chop */
      offset_delta = 19
    frame_type = 75 /* same_locals_1_stack_item */
      stack = [ class java/lang/InterruptedException ]
    frame_type = 4 /* same */

Note the difference in the locals part of the first entry. It's odd that the class GCTest entry doesn't appear anywhere without the initial assignment...

people

See more on this question at Stackoverflow