我有一个对象需要定期做一些工作,而对象本身还活着,所以我设计了类似下面的东西。基本上是一个Main类,它包含对ScheduledExecutorService实例的引用。在此示例中,所有定期工作都是将字符串打印到std。
我希望代码的行为如下:
然而,如果我运行这个程序,会发生什么,它会永远发生。基本上gc从不调用o1的终结器,因此,调度程序永远不会关闭,因此,即使主线程结束,程序仍然不会退出。
现在,如果我在test2()中注释掉o1.register,那么程序的行为就像它应该的那样,例如gc调用等。同样在调试器中,似乎只有在调用ScheduledExecutorService.schedule后才会创建一个实际的线程。
任何解释发生了什么?
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();
}
}
}
答案 0 :(得分:7)
两个问题:
System.gc()
调用被定义为JVM的建议,而不是命令。 API文档中的措辞是调用gc方法表明了这一点 Java虚拟机花费精力 回收未使用的物品...
答案 1 :(得分:3)
在玩WeakReference和ScheduledExecutorService之后,我想我现在对这个问题有了更好的理解。我的代码中的核心问题是以下方法register()。它使用匿名对象Runnable。像这样的匿名对象的问题是它会创建一个强引用回到父作用域。请记住,如果在父作用域“final”中创建字段,则可以在Runnable的run()方法中引用它们。如果我不从我的run()引用任何东西,我想我不会创建如此强大的引用。如本例所示,我在run()中所做的就是打印一些静态字符串。然而,根据观察到的行为,仍然会创建这样的参考。
private void register() {
_scheduler.scheduleWithFixedDelay(new Runnable() {
@Override public void run() {
System.out.println("!doing stuff...");
}
}, 1, 1, TimeUnit.SECONDS);
}
进行这种编程的正确方法是创建一个类并自己传入对象。您还需要保留一个弱引用。代码相当长,我将发布Runnable实现,它保持对域对象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());
}
}
}
现在在Main课程中,我有:
public class Main {
ScheduledExecutorService _poolInstance;
ScheduledFuture<?> _future;
public Main(ScheduledExecutorService p)
{
_poolInstance = p;
_future = _poolInstance.scheduleWithFixedDelay(new ResourceRefreshRunner(this), 1, 1, TimeUnit.SECONDS);
} ...
Main的终结者:
@Override
protected void finalize() throws Throwable {
try {
System.out.println("bye");
_future.cancel(true);
} finally {
super.finalize();
}
}
使用此设置,代码按预期运行。例如。当不再引用Main对象时,GC将启动并调用终结器。我做的另一个实验是没有_future.cancel(true);在finalize()中,当Main对象被GC编辑时,Runnable.run()中的弱引用不能再取消引用Main对象,但线程和任务仍在运行。