javajava-8jvmgarbage-collectionjava-23

Inner classes and garbage collection: behavior differences between Java 8 and latest Java


I had an interview today, and the interviewer presented the following code. They asked what the output would be in Java 8 and the latest version of Java.

From my understanding, since B is an inner class (non-static nested class), it holds a reference to the instance of A, which prevents A from being garbage collected (GC). This behavior is expected in Java 8. However, the interviewer demonstrated that A can be garbage collected in the latest version of Java (Java 23). He then asked in which version of Java this change occurred and why A can now be GC'd. I believe this behavior is either related to the new mechanism of inner classes (a is GC'd even when b is still alive. In principle, b should hold reference to a) or the new garbage collection process. However, I don't know the answer.

class A {
    class B {
        @Override
        protected void finalize() throws Throwable {
            System.out.println("B destroy");
        }
    }

    @Override
    protected void finalize() throws Throwable {
        System.out.println("A destroy");
    }
}

public class Solution {
    public static void main(String[] args) throws InterruptedException {
        A a = new A();
        A.B b = a.new B();
        a = null;
        System.gc();
        System.out.println("finish gc");
        Thread.sleep(1000);
        System.out.println("finish sleep");
    }
}

Output of Java 8

finish gc
finish sleep

Output of Java 23

finish gc
A destroy
finish sleep

Solution

  • By experimenting with each version of Java from 23 downwards, I discovered that this phenomenon started in Java 18. I then looked up the new features of Java 18.

    Enclosing Instance Fields Omitted from Inner Classes That Don't Use Them https://www.oracle.com/java/technologies/javase/18-relnote-issues.html#JDK-8271623

    Prior to JDK 18, when javac compiles an inner class it always generates a private synthetic field with a name starting with this$ to hold a reference to the enclosing instance, even if the inner class does not reference its enclosing instance and the field is unused. Starting in JDK 18, unused this$ fields are omitted; the field is only generated for inner classes that reference their enclosing instance.