I have an object that needs to do periodically do some work while the object itself is alive, so I designed something like the following. Basically a Main class which contains a reference to a ScheduledExecutorService instance. In this example, all the periodical work is to print a string to std.
I expect the code to behave like the following:
However, if I run this program, what happens is that it will go on FOREVER. Basically the gc never calls o1's finalizer and as a result, scheduler never shuts down and as a result, even when main thread end, the program still won't quit.
Now if I comment out the o1.register in test2(), the program behaves like it should, e.g. gc called etc. Also in debugger it seems only after a call to ScheduledExecutorService.schedule will an actual thread created.
Any explanation what's happening?
public class Main {
public static void main(String[] args) throws Exception {
test2();
System.gc();
System.out.println("Waiting for finalize to be called..");
Thread.sleep(5000);
}
private static void test2() throws Exception {
Main o1 = new Main();
o1.register();
Thread.sleep(5000);
}
private final ScheduledExecutorService _scheduler = Executors.newSingleThreadScheduledExecutor();
private void register() {
_scheduler.scheduleWithFixedDelay(new Runnable() {
@Override public void run() {
System.out.println("!doing stuff...");
}
}, 1, 1, TimeUnit.SECONDS);
}
@Override
protected void finalize() throws Throwable {
try {
System.out.print("bye");
_scheduler.shutdown();
} finally {
super.finalize();
}
}
}
Two problems:
System.gc()
call is defined as a suggestion to the JVM, not a command. The wording in the API doc isCalling the gc method suggests that the Java Virtual Machine expend effort toward recycling unused objects ...
After playing with WeakReference and ScheduledExecutorService, I think I have a better understanding of the problem now. The central problem in my code is the following method register(). It uses an anonymous object Runnable. The problem with anonymous object like this is it creates a strong reference back to the parent scope. Remember if you make fields in parent scope "final", you can reference to them from within run() method of Runnable. I thought I do not create such strong ref if I don't reference anything from my run(). As shown in this case, all I do in the run() is to print some static string out. However, according the the behavior observed, such reference is created nonetheless.
private void register() {
_scheduler.scheduleWithFixedDelay(new Runnable() {
@Override public void run() {
System.out.println("!doing stuff...");
}
}, 1, 1, TimeUnit.SECONDS);
}
The correct way of doing this kind of programming is to create a class and pass in your object yourself. You also need to keep only a weak reference. The code is rather long, I'll just post the Runnable implementation, which keeps a weak reference to the domain object Main.
private static class ResourceRefreshRunner implements Runnable
{
WeakReference<Main> _weakRef;
public ResourceRefreshRunner(Main o)
{
_weakRef = new WeakReference<Main>(o);
}
@Override
public void run() {
try {
Main m = _weakRef.get();
if (m != null)
m.shout();
else
System.out.println("object not there, but future is running. ");
} catch (Exception ex) {
System.out.println(ex.toString());
}
}
}
Now in the Main class, I have:
public class Main {
ScheduledExecutorService _poolInstance;
ScheduledFuture<?> _future;
public Main(ScheduledExecutorService p)
{
_poolInstance = p;
_future = _poolInstance.scheduleWithFixedDelay(new ResourceRefreshRunner(this), 1, 1, TimeUnit.SECONDS);
} ...
And the finalizer of Main:
@Override
protected void finalize() throws Throwable {
try {
System.out.println("bye");
_future.cancel(true);
} finally {
super.finalize();
}
}
With this setup, the code behaves as expected. E.g. when a Main object is no longer referenced, GC will kick in and the finalizer will get called. One more experiment I did is that without _future.cancel(true); in the finalize(), when the Main object is GC-ed, the weak reference in the Runnable.run() can't dereference to a Main object anymore, but the thread and tasks is still running.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With