I'm asking this question because I am creating a lot of executor services and while I may already have a memory leak somewhere that needs to be investigated, I think a recent change to the following code actually worsened it, hence I am trying to confirm what is going on:
@FunctionalInterface
public interface BaseConsumer extends Consumer<Path> {
@Override
default void accept(final Path path) {
String name = path.getFileName().toString();
ExecutorService service = Executors.newSingleThreadExecutor(runnable -> {
Thread thread = new Thread(runnable, "documentId=" + name);
thread.setDaemon(true);
return thread;
});
Future<?> future = service.submit(() -> {
baseAccept(path);
return null;
});
try {
future.get();
} catch (InterruptedException ex) {
Thread.currentThread().interrupt();
} catch (ExecutionException ex) {
throw new RuntimeException(ex);
}
}
void baseAccept(final Path path) throws Exception;
}
Then this Consumer<Path>
gets called on another thread pool with (usually) N=2 threads, I am not sure if that is relevant.
The question is: Does the ExecutorService service
go out of scope and get garbage collected once BaseConsumer#accept
has finished?
You can do this much simpler/faster
This way you can use a named thread without creating a new one.
Yes.
Indeed, the associated thread pool should also be garbage collected ... eventually.
The
ExecutorService
that is created byExecutors.newSingleThreadExecutor()
an instance ofFinalizableDelegatedExecutorService
. That class hasfinalize()
method that callsshutdown()
on the wrappedExecutorService
object. Provided that all outstanding tasks actually terminate, the service object will shut down its thread pool.(AFAIK, this is not specified. But it is what is implemented according to the source code, in Java 6 onwards.)
Yes it does. Calling
shutdown()
causes the threads to be released as soon as the outstanding tasks complete. That procedure starts immediately, whereas if you just left it to the garbage collector it wouldn't start until the finalizer was called.Now if the resources were just "ordinary" Java objects, this wouldn't matter. But in this case, the resource that you are reclaiming is a Java thread, and that has associate operating system resources (e.g. a native thread), and a non-trivial chunk of out-of-heap memory. So it is maybe worthwhile to do this.
But if you are looking to optimize this, maybe you should be creating a long-lived
ExecutorService
object, and sharing it across multiple "consumer" instances.