需要间歇性头痛的帮助。代码调用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
并使用第一种方法调用它,但我正在尝试理解现在的问题。
答案 0 :(得分:7)
Executors#newSingleThreadExecutor()
返回的ExecutorService
恰好是ThreadPoolExecutor
包裹在FinalizableDelegatedExecutorService
中,这是一个关闭ExecutorService
[Terminated, pool size = 0, active threads = 0, queued tasks = 0, completed tasks = 0]
的实现细节}}。我们可以告诉执行程序是从日志中关闭的
FinalizableDelegatedExecutorService
为什么要最终确定对象?你说
根据我的理解,GC不应该清理此执行程序 还没有超出范围,但从错误的日志来判断 似乎是这种行为。
这是一种常见的误解。 finalization是一个编译时功能,用于确定名称在何处可用于引用源代码中的某个实体。它与运行时垃圾收集无关。
垃圾收集(和终结)由对象的可达性控制。 Scope
当一个对象不再被引用时,它可以被回收 垃圾收集器。如果一个对象声明了一个终结器,那么终结器就是 在回收对象之前执行以给对象一个最后一个 有机会清理本来无法释放的资源。 当不再需要某个类时,可以将其卸载。
JVM不会垃圾收集The Java Language Specification states个对象
可到达对象是可以从任何活动线程的任何潜在持续计算中访问的任何对象。
正如已接受的答案(由Oracle开发人员)解释的那样
可达性分析允许
即使有最终确定和垃圾收集的对象 在堆栈中的局部变量中引用它
您所看到的是JVM决定通过实时线程的持续计算并最终确定ThreadPoolExecutor
(及其RejectedExecutionException
)不再可达。该操作会关闭执行程序,并在提交任务时抛出google-http-client
。
这是该实现的一个已知问题,并且finalize() called on strongly reachable object in Java 8已经开放讨论解决方案(基本上只是更好的文档)。
如果由我决定,FinalizableDelegatedExecutorService
会将其实施更改为使用未ExecutorService
包裹的bug JDK-8145304。 (我已经打开Executors.newFixedThreadPool(1)
来讨论图书馆的解决方案。)
我的建议是您创建自己的newFixedThreadPool
(可能包含executeAsync(Executor)
)并使用重载的get_the_tags()
方法。
问题已修复,请参阅this issue。