[Java] Hung JVM due to the threads stuck in pthread_cond_timedwait()

原文はこちら。
https://blogs.oracle.com/poonam/entry/hung_jvm_due_to_the

このエントリでは、Javaプロセスのハングが実際にはJava/JVMが原因ではなく、OSの問題が原因で起こるという複数のシナリオについて説明していきます。今回説明するどちらのハングもLinux OSで発生します。
それでは、最初のケースを見ていきましょう。この例では、スレッドがJavaの Thread.sleep() を呼び出すところでスタックしているようです。プロセスのスタックトレースから、2個のスレッドに関心を当ててみましょう。
Thread 26755: (state = IN_VM)
 - java.lang.Thread.interrupt0() @bci=0 (Interpreted frame)
 - java.lang.Thread.interrupt() @bci=51, line=852 (Interpreted frame)
 - oracle.core.ojdl.BufferedLogWriter.stopFlusher() @bci=17, line=380
(Interpreted frame)

Thread 21714: (state = BLOCKED)
 - java.lang.Thread.sleep(long) @bci=0 (Interpreted frame)
 - oracle.core.ojdl.BufferedLogWriter$Flusher.run() @bci=30, line=401
(Interpreted frame)
スレッド #26755 は、Thread.sleep() を実行しているスレッド #21714 に割り込みしようとしています。スレッド#21714はプロセス内の他スレッドが進行できないようにするBLOCKED状態に留まっているため、その結果プロセスがハングしています。
では、ネイティブ・スタックトレースを見てみましょう。
Thread 26755:
#0  0xf77e7430 in __kernel_vsyscall ()
#1  0x4dcc31b9 in __lll_lock_wait () from /lib/libpthread.so.0
#2  0x4dcbe550 in _L_lock_677 () from /lib/libpthread.so.0
#3  0x4dcbe421 in pthread_mutex_lock () from /lib/libpthread.so.0
#4  0x064ff034 in os::Linux::Event::unpark() ()
#5  0x064fc60a in os::interrupt(Thread*) ()

Thread 21714:
#0  0xf77e7430 in __kernel_vsyscall ()
#1  0x4dc0fc43 in __lll_lock_wait_private () from /lib/libc.so.6
#2  0x4db94348 in _L_lock_9835 () from /lib/libc.so.6
#3  0x4db91f27 in calloc () from /lib/libc.so.6
#4  0x4dcc0e1c in pthread_cond_timedwait@GLIBC_2.0 () from /lib/libpthread.so.0
#5  0x064fefa1 in os::Linux::Event::timedwait(timespec*) ()
#6  0x064fc2bf in os::sleep(Thread*, long long, bool) ()
#7  0x063d5d90 in JVM_Sleep ()
pthread_cond_timedwait() 関数に期間としてどのような値を渡したのか疑問に思われるかもしれません。このときの値は5秒でしたので、5秒後 pthread_cond_timedwait() は待機状態から復帰しているはずです。
pthread_cond_timedwait()は、他スレッドとの調整に利用するpthread_cond_t* condpthread_mutex_t* mutexという引数を受け入れます。
pthread_cond_timedwait(3) - Linux man page
https://linux.die.net/man/3/pthread_cond_timedwait
ではcondmutexの両引数を見てみましょう。
(gdb) print *((pthread_cond_t*)0xad533ff0)
$3 = {__c_lock = {__status = 0, __spinlock = 17}, __c_waiting = 0xad534050}

(gdb) print *((pthread_mutex_t*)0xad533fd8)
$4 = {__m_reserved = 2, __m_count = 0, __m_owner = 0x54d2, __m_kind = 0, __m_lock = {__status = 1, __spinlock = 0}} 
__m_owner = 0x54d2 = 21714

 (gdb) print *$11->_osthread->_interrupt_event
$14 = {<CHeapObj> = {<No data fields>}, _count = 0, _nParked = 1,
cachePad = {-2.3622328335110874e-90, 4.2439915835115547e-313,
    1.4480588116526359e-314, 4.2439915866735748e-313}, _mutex =
{{__m_reserved = 2, __m_count = 0, __m_owner = 0x54d2, __m_kind = 0,
__m_lock = {
        __status = 1, __spinlock = 0}}}, _cond = {{__c_lock = {__status
= 0, __spinlock = 17}, __c_waiting = 0xad534050}}, FreeNext = 0xbad, Immortal = 1}
上のデータから、スリープ状態にあるスレッドが_mutexを所有していること、割り込もうとしているスレッドが_mutexを取得してスリープ状態にあるスレッドにシグナルを送信しようと待機していることがわかります。よく見ると、スリープ状態のスレッドは、_mutexのリリースする前に(callocで)メモリアロケーションを完了するためにネイティブ・ヒープのロックを待っています。

そのため、スレッドがpthread_cond_timedwait()で無限にスタックするのは、JVMが問題なのではなく、Linuxのカーネルレベルのロック同期に伴う問題であることがわかりました。

では、別のケースを見ていきましょう。このケースでは、3個のスレッドが__pthread_cond_timedwait()でスタックしており、プロセス中の他のスレッドの進行を抑止しています。
----------------- 11374 -----------------
0x00000032f1c0ba0e      __pthread_cond_timedwait + 0x13e
0x00007fb1453bbf94      _ZN13ObjectMonitor4waitElbP6Thread + 0x224
0x00007fb1454a0d83      _ZN18ObjectSynchronizer4waitE6HandlelP6Thread + 0x53
0x00007fb14520b34b      JVM_MonitorWait + 0x1fb
0x00007fb13d010eee      * java.lang.Object.wait(long) bci:0 (Interpreted frame)
0x00007fb13d005a82      * java.lang.Thread.join(long) bci:70 line:1214 (Interpreted frame)
0x00007fb13d005a82      * iasauto.execution.GetCfgRunDTE.execute() bci:864 line:504 (Interpreted frame)
0x00007fb13d005a82      * iasauto.execution.RunOneJob.execute() bci:1675 line:1521 (Interpreted frame)
0x00007fb13d005a82      * iasauto.execution.RunOneJob.main(java.lang.String[]) bci:18 line:58 (Interpreted frame)
0x00007fb13d000438      <StubRoutines>
0x00007fb14519e8d0      _ZN9JavaCalls11call_helperEP9JavaValueP12methodHandleP17JavaCallArgumentsP6Thread + 0x1e0
0x00007fb1453ca829       _ZN2os20os_exception_wrapperEPFvP9JavaValueP12methodHandleP17JavaCallArgumentsP6ThreadES1_S3_S5_S7_  + 0x19
0x00007fb14519e6e5      _ZN9JavaCalls4callEP9JavaValue12methodHandleP17JavaCallArgumentsP6Thread + 0x25
0x00007fb1451d99f7       _Z17jni_invoke_staticP7JNIEnv_P9JavaValueP8_jobject11JNICallTypeP10_jmethodIDP18JNI_ArgumentPusherP6Thread  + 0x147
0x00007fb1451c753f      jni_CallStaticVoidMethod + 0x20f
0x0000000040002284      JavaMain + 0x2a4

----------------- 31490 -----------------
0x00000032f1c0ba0e      __pthread_cond_timedwait + 0x13e
0x00007fb1453bbf94      _ZN13ObjectMonitor4waitElbP6Thread + 0x224
0x00007fb1454a0d83      _ZN18ObjectSynchronizer4waitE6HandlelP6Thread + 0x53
0x00007fb14520b34b      JVM_MonitorWait + 0x1fb
0x00007fb13d010eee      * java.lang.Object.wait(long) bci:0 (Interpreted frame)
0x00007fb13d005a82      * java.lang.Thread.join(long) bci:70 line:1214 (Interpreted frame)
0x00007fb13d005a82      * iasauto.engine.LocalTaskProcess.execute() bci:70 line:206 (Interpreted frame)
0x00007fb13d005a82      * iasauto.engine.TaskProcess.executeLocal(boolean) bci:277 line:751 (Interpreted frame)
0x00007fb13d005a82      * iasauto.engine.TaskProcess.run() bci:631 line:289 (Interpreted frame)
0x00007fb13d005f5c      * java.lang.Thread.run() bci:11 line:682 (Interpreted frame)
0x00007fb13d000438      <StubRoutines>
0x00007fb14519e8d0      _ZN9JavaCalls11call_helperEP9JavaValueP12methodHandleP17JavaCallArgumentsP6Thread + 0x1e0
0x00007fb1453ca829       _ZN2os20os_exception_wrapperEPFvP9JavaValueP12methodHandleP17JavaCallArgumentsP6ThreadES1_S3_S5_S7_  + 0x19
0x00007fb14519e246       _ZN9JavaCalls12call_virtualEP9JavaValue11KlassHandle12symbolHandleS3_P17JavaCallArgumentsP6Thread  + 0x116
0x00007fb14519e2c7      _ZN9JavaCalls12call_virtualEP9JavaValue6Handle11KlassHandle12symbolHandleS4_P6Thread + 0x47
0x00007fb145230c12      _Z12thread_entryP10JavaThreadP6Thread + 0xa2
0x00007fb1454d3401      _ZN10JavaThread3runEv + 0x121

----------------- 11681 -----------------
0x00000032f1c0ba0e      __pthread_cond_timedwait + 0x13e
0x00007fb1453bbf94      _ZN13ObjectMonitor4waitElbP6Thread + 0x224
0x00007fb1454a0d83      _ZN18ObjectSynchronizer4waitE6HandlelP6Thread + 0x53
0x00007fb14520b34b      JVM_MonitorWait + 0x1fb
0x00007fb13d010eee      * java.lang.Object.wait(long) bci:0 (Interpreted frame)
0x00007fb13d005a82      * java.util.TimerThread.mainLoop() bci:201 line:509 (Interpreted frame)
0x00007fb13d005a82      * java.util.TimerThread.run() bci:1 line:462 (Interpreted frame)
0x00007fb13d000438      <StubRoutines>
0x00007fb14519e8d0      _ZN9JavaCalls11call_helperEP9JavaValueP12methodHandleP17JavaCallArgumentsP6Thread + 0x1e0
0x00007fb1453ca829       _ZN2os20os_exception_wrapperEPFvP9JavaValueP12methodHandleP17JavaCallArgumentsP6ThreadES1_S3_S5_S7_  + 0x19
0x00007fb14519e246       _ZN9JavaCalls12call_virtualEP9JavaValue11KlassHandle12symbolHandleS3_P17JavaCallArgumentsP6Thread  + 0x116
0x00007fb14519e2c7      _ZN9JavaCalls12call_virtualEP9JavaValue6Handle11KlassHandle12symbolHandleS4_P6Thread + 0x47
0x00007fb145230c12      _Z12thread_entryP10JavaThreadP6Thread + 0xa2
0x00007fb1454d3401      _ZN10JavaThread3runEv + 0x121
0x00007fb1453cbcdf      _Z10java_startP6Thread + 0x13f
これまでの調査から、これは以下のコミットで関連する修正が完了したLinuxカーネルの問題であると信じています。
futex: Ensure get_futex_key_refs() always implies a barrier
https://github.com/torvalds/linux/commit/76835b0ebf8a7fe85beb03c75121419a7dec52f0
まとめると、見かけ上JavaプロセスがハングしているのはJava/JVMの問題っぽく見えたとしても、本当の理由はそうではないことがあります。私たちはスレッドのネイティブスタックトレースをよく見て、何が原因でスレッドが停止しているのか、どこでスタックしているのか、を理解する必要があります。

0 件のコメント:

コメントを投稿