执行程序服务在超出范围时是否会被垃圾回收?

我问这个问题是因为我正在创建很多执行器服务,虽然我可能已经在某个地方有一个需要调查的内存泄漏,但我认为最近对以下代码的更改实际上使它变得更糟,因此我试图确认发生了什么:

@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;
}

然后,在另一个线程池上调用它(通常)N = 2个线程,我不确定这是否相关。Consumer<Path>

问题是:一旦完成,是否超出了范围收集了垃圾?ExecutorService serviceBaseConsumer#accept


答案 1

执行器服务是否超出范围,并在完成后进行垃圾回收?BaseConsumer.accept()

是的。

实际上,关联的线程池也应该被垃圾回收...最终。

由 的实例创建的。该类具有调用包装对象的方法。如果所有未完成的任务实际终止,服务对象将关闭其线程池。ExecutorServiceExecutors.newSingleThreadExecutor()FinalizableDelegatedExecutorServicefinalize()shutdown()ExecutorService

(AFAIK,未指定。但这是根据源代码实现的,在Java 6之前。


在围绕 future.get() 的 try-catch 中添加 finally { service.shutdown(); } 是否有助于更快地检索资源?(不一定是垃圾回收服务对象)。

是的,确实如此。调用会导致在未完成任务完成后立即释放线程。该过程会立即开始,而如果您只是将其留给垃圾回收器,则在调用终结器之前,它不会启动。shutdown()

现在,如果资源只是“普通”Java对象,这无关紧要。但在本例中,您要回收的资源是一个 Java 线程,它具有关联的操作系统资源(例如本机线程)和一个不平凡的堆外内存块。因此,这样做可能是值得的。

但是,如果您希望对此进行优化,也许您应该创建一个长期存在的对象,并在多个“使用者”实例之间共享它。ExecutorService


答案 2

我想让执行在命名线程上进行,这与它更容易记录有关。在任何一种情况下,此代码都应该有效。

你可以做得更简单/更快

Thread t = Thread.currentThread();
String name = t.getName();
try {
    t.setName("My new thread name for this task");
    // do task
} finally {
    t.setName(name);
}

这样,您就可以使用命名线程,而无需创建新线程。