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

Der*_*rek 4 java multithreading garbage-collection jvm google-http-client

需要帮助治疗间歇性头痛.代码调用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());
  }
Run Code Online (Sandbox Code Playgroud)

该调用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]
Run Code Online (Sandbox Code Playgroud)

根据我的理解,GC不应该清理这个执行器,因为它还没有超出范围,但是根据错误的日志判断这似乎是行为.

编辑:感谢您的快速回复.我可能需要澄清我不能在我的遗嘱中重现这一点,否则我可能会有更多的线索.代码通常运行正常但这个错误确实很少发生,我们所拥有的只是为症状粘贴的日志信息.

编辑2:澄清我粘贴的代码不是我的.这是google-http-java-client我正在使用的开源库.所以我无法改变这一点.我肯定可以创建一个长期的Executor自己,并用第一种方法调用它,但我想知道现在的问题是什么.

Sot*_*lis 7

ExecutorService通过返回Executors#newSingleThreadExecutor()恰好是ThreadPoolExecutor裹在FinalizableDelegatedExecutorService,是关闭的实现细节,它的包装ExecutorService最终确定.我们可以告诉执行程序从您的日志中关闭

[Terminated, pool size = 0, active threads = 0, queued tasks = 0, completed tasks = 0]
Run Code Online (Sandbox Code Playgroud)

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

根据我的理解,GC不应该清理这个执行器,因为它还没有超出范围,但是根据错误的日志判断这似乎是行为.

这是一个普遍的误解.Scope是一个编译时功能,用于确定名称在源代码中可用于引用某个实体的位置.它与运行时垃圾收集无关.

垃圾收集(和完成)由对象的可达性控制.Java语言规范说明

当不再引用对象时,垃圾收集器可以回收它.如果一个对象声明了一个终结器,则在回收该对象之前执行终结器,以便为该对象提供最后机会来清理本来不会被释放的资源.当不再需要某个类时,可以卸载它.

JVM不会垃圾收集可到达的对象

可到达对象是可以从任何活动线程的任何潜在继续计算中访问的任何对象.

正如接受的答案(由Oracle开发人员)解释的那样

可达性分析允许

对于要完成的对象和垃圾收集,即使堆栈中的局部变量中有对它的引用也是如此

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

这是该实现的已知问题,并且JDK-8145304被用来讨论解决方案(基本上只是更好的文档).

如果由我决定,google-http-client将改变他们的实现使用Executors.newFixedThreadPool(1)哪个没有包装FinalizableDelegatedExecutorService.(我已经打开这个问题来讨论该库的解决方案.)

我的建议是你创建自己的ExecutorService(可能有newFixedThreadPool)并使用重载 executeAsync(Executor)方法.


问题已修复,请参见此处.