When Is The Object Eligible For Garbage Collection?

In the code below, given that amethod has been called. At what point/line is the Object originally referenced by myObject, eligible for Garbage Collection?

class Test {
  private Object classObject;

  public void amethod() {
    Object myObject = new Object();
    classObject = myObject;
    myObject = null;
  }
}

And if classObject or amethod had an access modifier of public, protected, default or static, would it affect what point the Object is eligible for Garbage Collection? If so, how would it be affected?

  • My first thought is that the Object is eligible for Garbage Collection when the Test object is eligible for Garbage Collection.
  • But then again. The optimizer may know that the classObject is never read from in which case classObject = myObject; would be optimized out and myObject = null; is the point it is eligible for Garbage Collection.

Solution 1:

The object will not become a candidate for garbage collection until all references to it are discarded. Java objects are assigned by reference so when you had

   classObject = myObject;

You assigned another reference to the same object on the heap. So this line

   myObject = null;

Only gets rid of one reference. To make myObject a candidate for garbage collection, you have to have

  classObject = null;

Solution 2:

From Book OCA Java SE 7

An object is marked as eligible to be garbage collected when it can no longer be accessed, which can happen when the object goes out of scope. It can also happen when an object’s reference variable is assigned an explicit null value or is reinitialized.

Solution 3:

This is actually addressed precisely by the Java Language Specification, §12.6.1, Implementing Finalization :

Optimizing transformations of a program can be designed that reduce the number of objects that are reachable to be less than those which would naively be considered reachable. For example, a Java compiler or code generator may choose to set a variable or parameter that will no longer be used to null to cause the storage for such an object to be potentially reclaimable sooner.

Another example of this occurs if the values in an object's fields are stored in registers. The program may then access the registers instead of the object, and never access the object again. This would imply that the object is garbage. …

But

… Note that this sort of optimization is only allowed if references are on the stack, not stored in the heap.

For example, consider the Finalizer Guardian pattern:

   class Foo {
       private final Object finalizerGuardian = new Object() {
           protected void finalize() throws Throwable {
               /* finalize outer Foo object */
           }
       }
   } 

The finalizer guardian forces super.finalize to be called if a subclass overrides finalize and does not explicitly call super.finalize.

If these optimizations are allowed for references that are stored on the heap, then a Java compiler can detect that the finalizerGuardian field is never read, null it out, collect the object immediately, and call the finalizer early. This runs counter to the intent: the programmer probably wanted to call the Foo finalizer when the Foo instance became unreachable. This sort of transformation is therefore not legal: the inner class object should be reachable for as long as the outer class object is reachable.

This example can be applied 1:1 to your example, as long as the object is referenced by the instance field classObject, it can not get garbage collected earlier than the Test instance containing the reference.

Note, however, that the aggressive optimizations mentioned in the specification are still allowed, when being applied to the code using the Test instance. The earlier-than-expected collection may happen, as long as both, the Test instance and the referenced object are collected together. In this case, the following aspect specified in §12.6 applies:

The Java programming language imposes no ordering on finalize method calls. Finalizers may be called in any order, or even concurrently.

So it’s perfectly possible that the Test instance is collected earlier than the object referenced by classObject whereas the “inner” object’s finalizer is invoked earlier. The only thing that is guaranteed, is, that when the inner object’s finalizer runs, the outer object is unreachable (or has a pending or concurrent finalization). Since in your example, neither has a non-trivial finalizer, that doesn’t matter anyway…