4

间歇性头痛需要帮助。代码正在调用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并用第一种方法调用它,但我正在尝试了解现在的问题。

4

1 回答 1

7

ExecutorService返回的 by恰好Executors#newSingleThreadExecutor()ThreadPoolExecutor包装在 a 中,这是一个在finalizationFinalizableDelegatedExecutorService时关闭其包装的实现细节。我们可以从您的日志中得知执行程序已关闭ExecutorService

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

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

根据我的理解,GC 不应该清理这个执行程序,因为它还没有超出范围,但从错误日志来看,这似乎是行为。

这是一个普遍的误解。作用域是一个编译时特性,它决定了一个名字可以用来在哪里引用源代码中的某个实体。它与运行时垃圾收集无关。

垃圾收集(和终结)由对象的可达性控制。Java 语言规范声明

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

JVM 不会垃圾收集可达对象

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

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

可达性分析允许

即使堆栈上的局部变量中有对它的引用,也要完成一个对象并进行垃圾收集

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

这是该实现的一个已知问题,并且打开了错误 JDK-8145304以讨论解决方案(本质上只是更好的文档)。

如果由我决定,google-http-client是否会将他们的实现更改为使用Executors.newFixedThreadPool(1)未用FinalizableDelegatedExecutorService. (我已经打开这个问题来讨论图书馆的解决方案。)

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


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

于 2017-09-27T22:22:55.097 回答