J. Doe
J. Doe

Reputation: 61

java finalizer and gc

Time to question JAVA System.GC() and System.runFinilizer

public interface SomeAction {
    public void doAction();
}

public class SomePublisher {
    private List<SomeAction> actions = new ArrayList<SomeAction>();

    public void subscribe(SomeSubscriber subscriber) {
        actions.add(subscriber.getAction());
    }
}

public class SomeSubscriber {
    public static int Count;

    public SomeSubscriber(SomePublisher publisher) {
        publisher.subscribe(this);
    }

    public SomeAction getAction() {
        final SomeSubscriber me = this;
        class Action implements SomeAction {

            @Override
            public void doAction() {
               me.doSomething();
            }
        }

        return new Action();
    }

    @Override
    protected void finalize() throws Throwable {
        SomeSubscriber.Count++;
    }

    private void doSomething() {
        // TODO: something
    }
}

Now I'm trying to force GC and Finalizer within main block.

 SomePublisher publisher = new SomePublisher();

        for (int i = 0; i < 10; i++) {
            SomeSubscriber subscriber = new SomeSubscriber(publisher);
            subscriber = null;
        }

        System.gc();
        System.runFinalization();

        System.out. println("The answer is: " + SomeSubscriber.Count);

Since JAVA GC call is not guaranteed to be called (as explained on javadoc and Since JAVA GC call is not guaranteed to be called (as explained on javadoc and When is the finalize() method called in Java?,

my init thought was that it would out put random SomeSubscriber.Count. (At least '1' as force by System.GC and finalizer.)

Instead it's always 0.

Can anyone explain this behavior?

(plus, does static member field exists independant of class instances and never be destroyed during code execution?)

Upvotes: 4

Views: 336

Answers (1)

RealSkeptic
RealSkeptic

Reputation: 34618

Your test has a flaw - even assuming that calling System.gc() and System.runFinalization() would actually run the GC and finalization, the instances you have created are not candidates for garbage collection, and therefore, will not be finalized nor collected.

You run this line 10 times:

SomeSubscriber subscriber = new SomeSubscriber(publisher);

This invokes SomeSubscriber's constructor, which says:

publisher.subscribe(this);

So, the publisher object is given a reference to the object currently being constructed. What does it do with it?

actions.add(subscriber.getAction());

OK, so it calls the getAction() method on the subscriber, and stores the result. What does getAction() do?

public SomeAction getAction() {
    final SomeSubscriber me = this;
    class Action implements SomeAction {

        @Override
        public void doAction() {
           me.doSomething();
        }
    }

    return new Action();
}

It creates an instance of a local class. That instance holds an instance of the enclosing SomeSubscriber object. In fact, it has two such instances - the me, and the implicit reference to the enclosing instance that every inner class has. Local classes are inner classes!

Thus, when you store a list of your actions in the publisher instance, you also store references to all your subscribers in it. When you run System.gc() and System.runFinalization(), the publisher instance is still live, and therefore those references are still live, so none of your SomeSubscriber instances is actually eligible for garbage collection.

Make sure you also assign publisher = null, and then maybe you'll be able to see the finalization running. I'd also recommend declaring Count to be volatile (and not calling it Count but count - variables are supposed to start with a lowercase letter), as the finalizer usually runs in a different thread.

Upvotes: 2

Related Questions