Reputation: 61
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
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