4

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?)

Community
  • 1
  • 1
J. Doe
  • 61
  • 5
  • 1
    If you don't know what static fields are, you shouldn't be worrying about finalizers or the gc. – Kayaman Dec 28 '15 at 19:52
  • Static field is class variable that exists without instantiating the class that shared by class instances. – J. Doe Dec 28 '15 at 19:57
  • What part of "not guaranteed to be called" did you not understand? – Kayaman Dec 28 '15 at 20:03
  • You shouldn't call `System.gc()` explicitly. In the best case, it will do nothing, in the worst case, it will always result in a full stop-the-world GC (e.g. when the CMS collector is in use). – Mick Mnemonic Dec 28 '15 at 20:13
  • if you see techloris_109 example from http://stackoverflow.com/questions/2506488/when-is-the-finalize-method-called-in-java, System.GC() is called manually and finalize block runs after nulling the instance. Why doesn't the same thing happens in my case? – J. Doe Dec 28 '15 at 20:18

1 Answers1

2

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.

RealSkeptic
  • 33,993
  • 7
  • 53
  • 79
  • This is it! So problem was not about GC but Local class references! – J. Doe Dec 28 '15 at 20:30
  • Even if you solve reference issue, there is no guaranty on finalize() method execution. Have a look at : http://stackoverflow.com/questions/20829228/shutdown-hook-vs-finalizer-method/34472233#34472233 – Ravindra babu Dec 29 '15 at 09:30
  • @ravindra That's true, and still, these tests usually have some success and are an important learning tool. It's important to do them correctly, though. – RealSkeptic Dec 29 '15 at 10:06