我有一个最近开始使用 100% CPU 时间的 Java 进程。使用jdb
我发现这是由ThreadPoolExecutor
重复创建线程引起的。
有问题的执行人定义为:
private final ScheduledExecutorService _scheduler = Executors.newScheduledThreadPool(0, new NamedThreadFactory("OrderServiceScheduler", true, null));
唯一计划的任务是:
_scheduler.scheduleAtFixedRate(new Runnable() { @Override public void run() { s_log.info("Resetting order books"); _liveOrderBook.clear(); } },
midnightTodayInMs,
TimeUnit.MILLISECONDS.convert(1, TimeUnit.DAYS),
TimeUnit.MILLISECONDS);
日志语句永远不会被打印(我有 99% 的把握INFO
为此记录器启用了级别日志记录)。
我第一次trace go methods
在 jdb 中运行,看到了很多行,例如:
Method entered: "thread=OrderServiceScheduler-thread-22237794", com.kbcfp.util.NamedThreadFactory.newThread(), line=45 bci=0
Method entered: "thread=OrderServiceScheduler-thread-22237794", org.apache.log4j.helpers.ThreadLocalMap.childValue(), line=34 bci=0
Method exited: return value = null, "thread=OrderServiceScheduler-thread-22237794", org.apache.log4j.helpers.ThreadLocalMap.childValue(), line=38 bci=15
Method exited: return value = instance of java.lang.Thread(name='OrderServiceScheduler-thread-22237795', id=6388), "thread=OrderServiceScheduler-thread-22237794", com.kbcfp.util.NamedThreadFactory.newThread(), line=52 bci=68
所以我设置了一个断点org.apache.log4j.helpers.ThreadLocalMap.childValue
并继续进行,直到线程意外停止执行(紧接在最后一个next, thread
OrderServiceScheduler-thread-151389734 线程isn't listed in the output of
之后):
> stop in org.apache.log4j.helpers.ThreadLocalMap.childValue
Set breakpoint org.apache.log4j.helpers.ThreadLocalMap.childValue
>
Breakpoint hit: "thread=OrderServiceScheduler-thread-151389734", org.apache.log4j.helpers.ThreadLocalMap.childValue(), line=34 bci=0
OrderServiceScheduler-thread-151389734[1] clear org.apache.log4j.helpers.ThreadLocalMap.childValue
Removed: breakpoint org.apache.log4j.helpers.ThreadLocalMap.childValue
OrderServiceScheduler-thread-151389734[1] where
[1] org.apache.log4j.helpers.ThreadLocalMap.childValue (ThreadLocalMap.java:34)
[2] java.lang.ThreadLocal$ThreadLocalMap.<init> (ThreadLocal.java:353)
[3] java.lang.ThreadLocal$ThreadLocalMap.<init> (ThreadLocal.java:261)
[4] java.lang.ThreadLocal.createInheritedMap (ThreadLocal.java:236)
[5] java.lang.Thread.init (Thread.java:401)
[6] java.lang.Thread.<init> (Thread.java:652)
[7] com.kbcfp.util.NamedThreadFactory.newThread (NamedThreadFactory.java:45)
[8] java.util.concurrent.ThreadPoolExecutor$Worker.<init> (ThreadPoolExecutor.java:598)
[9] java.util.concurrent.ThreadPoolExecutor.addWorker (ThreadPoolExecutor.java:913)
[10] java.util.concurrent.ThreadPoolExecutor.processWorkerExit (ThreadPoolExecutor.java:992)
[11] java.util.concurrent.ThreadPoolExecutor.runWorker (ThreadPoolExecutor.java:1,128)
[12] java.util.concurrent.ThreadPoolExecutor$Worker.run (ThreadPoolExecutor.java:603)
[13] java.lang.Thread.run (Thread.java:722)
OrderServiceScheduler-thread-151389734[1] next
>
Step completed: "thread=OrderServiceScheduler-thread-151389734", org.apache.log4j.helpers.ThreadLocalMap.childValue(), line=35 bci=5
OrderServiceScheduler-thread-151389734[1] next
>
Step completed: "thread=OrderServiceScheduler-thread-151389734", org.apache.log4j.helpers.ThreadLocalMap.childValue(), line=38 bci=14
OrderServiceScheduler-thread-151389734[1] next
>
Step completed: "thread=OrderServiceScheduler-thread-151389734", com.kbcfp.util.NamedThreadFactory.newThread(), line=45 bci=40
OrderServiceScheduler-thread-151389734[1] next
>
Step completed: "thread=OrderServiceScheduler-thread-151389734", com.kbcfp.util.NamedThreadFactory.newThread(), line=48 bci=41
OrderServiceScheduler-thread-151389734[1] next
>
Step completed: "thread=OrderServiceScheduler-thread-151389734", com.kbcfp.util.NamedThreadFactory.newThread(), line=49 bci=49
OrderServiceScheduler-thread-151389734[1] next
>
Step completed: "thread=OrderServiceScheduler-thread-151389734", com.kbcfp.util.NamedThreadFactory.newThread(), line=52 bci=67
OrderServiceScheduler-thread-151389734[1] next
>
我们的NamedThreadFactory
班级没有做任何令人兴奋的事情:
44: public Thread newThread(Runnable r) {
45: Thread t = new Thread(_group, r,
46: _namePrefix + _threadNumber.getAndIncrement(),
47: 0);
48: t.setDaemon(_makeDaemon);
49: if(_overridePriority != null) {
50: t.setPriority(_overridePriority);
51: }
52: return t;
53: }
据我所知,下一行执行应该是 914 of ThreadPoolExecutor
,但是线程反而死掉了。为什么?
作为参考,这是在 Solaris x86 主机上的 JDK 1.7.0_07 上运行的。
我所做的唯一更改是添加了 ActiveMQ 的客户端库。我怀疑这与在类路径中添加 slf4j (特别slf4j-api-1.6.6.jar
是 和slf4j-log4j12-1.6.6.jar
)有关,但我无法证明这一点。
更新
我已将执行的任务更改为单独的类:
public class TestingFoo implements Runnable
{
private final Logger s_log;
public TestingFoo(final Logger log)
{
s_log = log;
}
@Override
public void run()
{
try
{
s_log.info("Resetting order books");
// _liveOrderBook.clear();
}
catch (final Throwable t)
{
t.printStackTrace();
}
}
}
我jdb
在该行设置了一个断点,log.info
它没有被击中。此外,我没有看到在 stderr (或 log4j 文件)上打印堆栈跟踪。此外,我在s_log.info
安排任务之前放了另一行,那就是行
我现在正在下载 JDK 1.7.0_21 看看这是否有所作为。
更新 2
这是由于将核心池大小设置为零,因为彼得劳里谈到了他的答案。但是,它并没有导致什么都不运行,而是导致ThreadPoolExecutor
'runWorker
方法立即退出。该方法的要点如下:
runWorker(Worker) {
...
while (task != null || (task = getTask()) != null) {
...
task.run();
...
}
processWorkerExit(..);
}
设置核心池大小会导致getTask
返回null
而不阻塞要提交的任务。这会导致循环退出,processWorkerExit
然后被调用,从而创建另一个线程来替换退出的线程。
我相信我在调试器中看到的问题是由于 JIT 的代码。我放了一个Thread.sleep
线程工厂中添加了一条语句来减慢线程的创建速度,这意味着我有时间在应用任何优化之前附加调试器。
TL;DR:我是个白痴。
更新 3
对于将来可能遇到此问题的任何人...关于 SO 的另一个类似问题提到此行为在 Java 7 中已发生变化。在更改之前,正如 Peter Lawrey 所建议的那样,核心大小为零会导致没有线程永远被创建(错误报告)。进行了一项更改以确保创建一个线程,这似乎具有导致线程被重复创建的效果。这可以使用与错误报告中的测试用例非常相似的代码来重现:
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
class ScheduledExecutorTest {
public static void main(String[] args) throws InterruptedException {
final ScheduledExecutorService ex = Executors.newScheduledThreadPool(0);
ex.scheduleWithFixedDelay(new Runnable() {
@Override
public void run() {
System.out.println("hello");
}
}, 1, 1, TimeUnit.SECONDS);
while (true)
Thread.sleep(1000);
}
}