Does the future object returned by executorService.submit(Runnable) hold any reference to the runnable object?
Asked Answered
J

2

14

Let's assume we have the following code:

List<Future<?>> runningTasks;
ExecutorService executor;
...
void executeTask(Runnable task){
    runningTasks.add(executor.submit(task));
}

My questions are:

  1. Does runningTasks hold a reference to the task object?
  2. How long does it hold it for? Does it still hold it after the task is complete?
  3. In order to avoid memory leaks do I have to take care to remove the future that was added to the list?
Judaea answered 11/4, 2014 at 13:53 Comment(5)
Usually, yes. As long as the task is running, that won’t be important as it is referenced by the executing thread anyway. And after its completion, I would just remove the Future from a list that is called runningTasksMustachio
Can I make the list to hold weak references instead? Something like List<WeakReference<Future<?>>>?Judaea
You can do. But it would make me wonder why you are storing the Futures in a list in the first place.Mustachio
Because at certain point I need to cancel certain tasks.Judaea
Then, List<WeakReference<Future<?>>> will work. It will allow the Futures to get gc’ed but you have to remove the WeakReference instance manually (though a WeakReference itself does not take much space). An alternative would be Collections.newSetFromMap(new WeakHashMap<Future<?>,Boolean>()) to create a Set<Future<?>> which allows its elements to get gc’ed. Can’t be easier…Mustachio
A
2

Until when the executor or the Future object holds a reference to it is an implementation detail. Therefore, if your tasks use a lot of memory such that you have to worry about memory usage, you should explicitly clean up in your task before the task completes.

If you look at OpenJDK 1.6 source code of ThreadPoolExecutor, you can see that in fact the underlying Future object actually retains a reference to the underlying callable object indefinitely (i.e. as long as there is a strong reference to the Future object, the callable cannot be GCd). This is true for 1.7 as well. As of 1.8, the reference to it is nulled out. However, you can't control on which implementation of ExecutorService your task will run.

Using a WeakReference should work in practice as the Future and thus the Callable object can be GCd once the task is done and sane implementations of ExecutorService should lose reference to it when the task is done. Strictly speaking this still depends on the implementation of the ExecutorService though. Moreover, use of WeakReference can add surprisingly large overhead. You are much better off simply explicitly cleaning up whatever object that's taking up a lot of memory. Conversely, if you aren't allocating large objects then I wouldn't worry.

Of course this discussion is entirely different from the memory leak you'll have if you keep adding futures to your list without ever removing any. If you do this even using WeakReference wouldn't help; you'll still have a memory leak. For this, simply iterate through the list and remove futures that are already done and thus are of no use. It's really fine to do this every time unless you have a ridiculously large queue size as this is very quick.

Annetteannex answered 16/7, 2014 at 18:53 Comment(5)
In ThreadPoolExecutor I don't see anywhere Future object retains a reference to the underlying callable object indefinitely.Cesena
@Vipin: ThreadPoolExecutor returns a FutureTask instance as the Future. This FutureTask implementation in turn has a field FutureTask.Sync.callable which points to the underlying callable. Both the field that holds the sync object and its field that points to the underlying callable is final. See -> grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/…Annetteannex
in JDK 7 it stores reference in callable reference variable of FutureTask but if you look at FutureTask.finishCompletion() method it is being set as null. and finishCompletetion is called in cancel() and run() both so ultimately it becomes null and no danger of memory leak.Cesena
@Vipin: Which JDK are you talking about? I'm looking at OpenJDK, maybe you are looking at Oracle JDK or IBM JDK? Also, it could have changed in a minor version, or change in future versions like JDK 9. And what happens with other ExecutorServices like Netty's MemoryAwareExecutor or Guava's ListeningExecutorservice? You can't know how all implementation behaves. That's why you shouldn't rely on a coincidence of implementation.Annetteannex
I have given example of FutureTask implementation only which is written by Written by Doug Lea and Team. Same implementation is used in OpenJDK and Oracle JDK. I am using Oracle JDK 1.7.0_60.Cesena
C
-1
  1. Does runningTasks( Future ) hold a reference to the task object? --- Yes
  2. How long does it hold it for? Does it still hold it after the task is complete? --- It holds task reference till the task is complete after that it is removed from queue of ThreadPoolExecutor also in FutureTask.finishCompletion() method we set callable(task) reference to null. FutureTask.finishCompletion() is called inside run and cancel methods of FuturetTask. Thus in run and cancel both cases future doesn't have reference to task.
  3. In order to avoid memtory leaks do I havie to take care to remove the future that was added to the list? --- You are safe if you are using Future.

If you use ScheduledFuture then you may face memory leak issue as ScheduledFuture.cancel() or Future.cancel() in general does not notify its Executor that it has been cancelled and it stays in the Queue until its time for execution has arrived. It's not a big deal for simple Futures but can be a big problem for ScheduledFutures. It can stay there for seconds, minutes, hours, days, weeks, years or almost indefinitely depending on the delays it has been scheduled with.

For more details with example of memory leak situation and its solution see my other answer.

Cesena answered 16/7, 2014 at 18:20 Comment(6)
I don't think this is correct. At least in OpenJDK 1.6, the Future object created by ThreadPoolExecutor retains a reference to the underlying callable even after the task is done.Annetteannex
@EnnoShioji look at the getTask() method of ThreadPoolExecutor.Worker class. It has poll() and take() calls which Retrieves and removes the head of queue.Cesena
Sure, it gets removed from the internal queue, but the Future object, which is a FutureTask, still has a field that refers to the underlying callable as can be seen here: grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/…Annetteannex
The field is FutureTask.Sync.callableAnnetteannex
In OpenJDK 1.7 it's still there, and in 1.8 they started to null it out it seems (grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/…). This is however exactly why you don't want to rely on implementation artefact.Annetteannex
@EnnoShioji in JDK 7 it stores reference in callable reference variable of FutureTask but if you look at FutureTask.finishCompletion() method it is being set as null. and finishCompletetion is called in cancel() and run() both so ultimately it becomes null and no danger of memory leak.Cesena

© 2022 - 2024 — McMap. All rights reserved.