垃圾收集似乎关闭了本地执行程序并导致 RejectedExecutionException

Garbage collection seems to shutdown local executor and cause RejectedExecutionException

间歇性头痛需要帮助。代码调用 com.google.api.client.http.HttpRequest#executeAsync() 基本上具有以下逻辑,

  @Beta
  public Future<HttpResponse> executeAsync(Executor executor) {
    FutureTask<HttpResponse> future = new FutureTask<HttpResponse>(new Callable<HttpResponse>() {

      public HttpResponse call() throws Exception {
        return execute();
      }
    });
    executor.execute(future);
    return future;
  }

  @Beta
  public Future<HttpResponse> executeAsync() {
    return executeAsync(Executors.newSingleThreadExecutor());
  }

调用有时会遇到 java.util.concurrent.RejectedExecutionException,从日志来看,当这种情况发生时,它总是伴随着垃圾回收。下面是发生这种情况时的日志模式示例,

2017-09-26 11:04:56.039186 2017-09-26T11:04:56.012+0000: [GC pause (G1 Evacuation Pause) (young) 213M->50M(300M), 0.0262262 secs]
2017-09-26 11:04:56.048210 java.util.concurrent.RejectedExecutionException: Task java.util.concurrent.FutureTask@71a0a39 rejected from java.util.concurrent.ThreadPoolExecutor@36c306aa[Terminated, pool size = 0, active threads = 0, queued tasks = 0, completed tasks = 0]
2017-09-26 11:04:56.048212      at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2063) ~[?:1.8.0_141]
2017-09-26 11:04:56.048214      at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:830) ~[?:1.8.0_141]
2017-09-26 11:04:56.048216      at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1379) ~[?:1.8.0_141]
2017-09-26 11:04:56.048218      at java.util.concurrent.Executors$DelegatedExecutorService.execute(Executors.java:668) ~[?:1.8.0_141]
2017-09-26 11:04:56.048220      at com.google.api.client.http.HttpRequest.executeAsync(HttpRequest.java:1085) ~[google-http-client-1.21.0.jar:1.21.0]
2017-09-26 11:04:56.048222      at com.google.api.client.http.HttpRequest.executeAsync(HttpRequest.java:1099) ~[google-http-client-1.21.0.jar:1.21.0]

根据我的理解,GC 不应清理此执行程序,因为它尚未超出范围,但根据错误日志判断,这似乎是行为。

编辑:感谢您的快速回复。我可能需要澄清我不能随意复制这个,否则我可能会有更多线索。代码通常运行良好,但这种错误很少发生,我们所拥有的只是我为症状粘贴的日志信息。

EDIT2:澄清我粘贴的代码不是我的。这是我正在使用的开源库 google-http-java-client。所以我没有办法改变它。我当然可以自己创建一个长期 Executor 并使用第一种方法调用它,但我现在正在尝试了解问题所在。

Executors#newSingleThreadExecutor() happens to be ThreadPoolExecutor wrapped in a FinalizableDelegatedExecutorService, an implementation detail that shuts down its wrapped ExecutorService on finalization返回的ExecutorService。我们可以从您的日志中得知执行程序已关闭

[Terminated, pool size = 0, active threads = 0, queued tasks = 0, completed tasks = 0]

为什么要最终确定对象?你说

Based on my understanding GC shouldn't clean up this executor since it hasn't gone out of scope yet, but judging by the log of the error this seems to be the behavior.

这是一个常见的误解。 Scope 是一个编译时特性,它决定了一个名称可以用来指代源代码中某个实体的位置。它与运行时垃圾收集无关。

垃圾收集(和终结)由对象的可达性控制。 The Java Language Specification states

When an object is no longer referenced, it may be reclaimed by the garbage collector. If an object declares a finalizer, the finalizer is executed before the object is reclaimed to give the object a last chance to clean up resources that would not otherwise be released. When a class is no longer needed, it may be unloaded.

JVM 不会垃圾回收 reachable 个对象

A reachable object is any object that can be accessed in any potential continuing computation from any live thread.

如该问题的已接受答案(由 Oracle 开发人员提供)中所述

  • finalize() called on strongly reachable object in Java 8

可达性分析允许

for an object to be finalized and garbage collected even if there are references to it in local variables on the stack

您看到的是 JVM 做出决定,即通过实时线程的持续计算并最终确定它不再可以访问 FinalizableDelegatedExecutorService(及其 ThreadPoolExecutor)。该操作会关闭执行程序,并在提交任务时抛出 RejectedExecutionException

这是该实现的一个已知问题,bug JDK-8145304 已开放讨论解决方案(本质上只是更好的文档)。

如果由我决定,google-http-client 会更改他们的实现以使用 Executors.newFixedThreadPool(1) which isn't wrapped with a FinalizableDelegatedExecutorService. (I've opened this issue 来讨论库的解决方案。)

我的建议是您创建自己的 ExecutorService(也许使用 newFixedThreadPool)并使用重载的 executeAsync(Executor) 方法。


问题已解决,请参阅 here