垃圾回收似乎关闭了本地执行程序并导致拒绝执行异常



间歇性头痛需要帮助。代码调用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 不应该清理这个执行器,因为它还没有超出范围,但从错误的日志来看,这似乎是行为。

编辑:感谢您的快速回复。我可能需要澄清我也不能按照我的意愿重现它,否则我可能会有更多的线索。代码通常运行良好,但此错误确实很少发生,我们所拥有的只是我为症状粘贴的日志信息。

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

Executors#newSingleThreadExecutor()返回的ExecutorService恰好ThreadPoolExecutor包装在一个FinalizableDelegatedExecutorService中,这是一个在最终确定时关闭其包装ExecutorService的实现细节。我们可以从您的日志中判断执行器已关闭

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

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

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

这是一个常见的误解。范围是一种编译时功能,用于确定名称可用于引用源代码中的某个实体的位置。它与运行时垃圾回收无关。

垃圾回收(和完成)由对象的可访问性控制。Java 语言规范声明

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

JVM 不会垃圾回收可访问的对象

可访问对象

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

如 Oracle 开发人员对此问题的已接受答案中所述

  • finalize() 在 Java 8 中调用了强可访问的对象

可达性分析允许

对于要最终确定的对象并收集垃圾,即使有 在堆栈上的局部变量中引用它

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

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

如果由我决定,google-http-client会更改他们的实现以使用未用FinalizableDelegatedExecutorService包装的Executors.newFixedThreadPool(1)。(我打开这个问题是为了讨论库的解决方案。

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


此问题已修复,请参阅此处。

相关内容

  • 没有找到相关文章

最新更新