假设我们有以下代码:
List<Future<?>> runningTasks;
ExecutorService executor;
...
void executeTask(Runnable task){
runningTasks.add(executor.submit(task));
}
我的问题是:
runningTasks
是否持有对task
对象的引用?- 它会持有多久?任务完成后是否仍然持有它?
- 为避免内存泄漏,我是否需要注意删除已添加到列表中的未来对象?
假设我们有以下代码:
List<Future<?>> runningTasks;
ExecutorService executor;
...
void executeTask(Runnable task){
runningTasks.add(executor.submit(task));
}
我的问题是:
runningTasks
是否持有对 task
对象的引用?直到执行器或Future
对象保留对其的引用是一项实现细节。因此,如果您的任务使用了大量内存,以至于需要担心内存使用情况,则应在任务完成之前显式清理任务中的内存。
如果您查看ThreadPoolExecutor
的OpenJDK 1.6源代码,实际上可以看到底层Future
对象无限期地保留对底层可调用对象的引用(即只要Future
对象有强引用,就不能对可调用对象进行垃圾回收)。这也适用于1.7版本。从1.8开始,对其的引用被置为空。但是,您无法控制任务将在哪个ExecutorService
实现上运行。
在实践中,使用WeakReference
应该有效,因为Future
和因此Callable
对象可以在任务完成后进行垃圾回收,并且合理的ExecutorService
实现应该在任务完成时失去对它的引用。严格来说,这仍取决于ExecutorService的实现。此外,使用WeakReference可能会增加意外的大开销。如果您只是显式清理占用大量内存的对象,那么效果会更好。相反,如果您没有分配大型对象,则不必担心。
当然,这个讨论与在列表中保留未删除任何内容的future所导致的内存泄漏完全不同。即使使用WeakReference
也无济于事;您仍将面临内存泄漏问题。为此,请简单地遍历列表并删除已完成且无用的futures。每次执行此操作都是可以的,除非队列大小非常大,因为这非常快速。
FutureTask.Sync.callable
,它指向底层的callable。持有同步对象的字段和指向底层callable的字段都是final的。请参见->http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/6-b14/java/util/concurrent/FutureTask.java#FutureTask.Sync.%3Cinit%3E%28java.util.concurrent.Callable%29 - Enno Shioji如需更多详细信息,包括内存泄漏情况及其解决方案示例,请参阅我的其他答案。
ThreadPoolExecutor
创建的Future
对象在任务完成后仍会保留对底层可调用对象的引用。 - Enno Shioji
Future
即可... - HolgerList<WeakReference<Future<?>>>
就可以工作了。它将允许Future
被垃圾回收,但您必须手动删除WeakReference
实例(尽管WeakReference
本身不占用太多空间)。另一种选择是使用Collections.newSetFromMap(new WeakHashMap<Future<?>,Boolean>())
创建一个Set<Future<?>>
,它允许其元素被垃圾回收。再也没有更简单的方法了... - Holger