最近、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);
INFO
ログ ステートメントが出力されることはありません (このロガーでレベル ロギングが有効になっていることは 99% 確信しています)。
私は最初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-api-1.6.6.jar
私の疑いでは、これは slf4j (具体的には 、および) がクラスパスに追加されたことと関係があると思われますが、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
これは、Peter Lawrey が回答に触れたように、コア プール サイズをゼロに設定したことが原因でした。ただし、何も実行しないという効果を持つ代わりに、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 で変更されたことに言及する別の同様の質問があります。これまでに作成されました (バグレポート)。1つのスレッドが確実に作成されるように変更が加えられました。これにより、スレッドが繰り返し作成される効果があるようです。これは、バグ レポートのテスト ケースと非常によく似たコードで再現できます。
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);
}
}