Java线上问题排障:Linux内核bug引发JVM死锁导致线程假死
?Java本質(zhì)上還是離不開操作系統(tǒng),一來Java源碼是用C/C++實現(xiàn)的,二來java進程還是需要依附于操作系統(tǒng)和硬件資源,有時候一些問題是操作系統(tǒng)級別導(dǎo)致的,下面的整個事件是源自一則真實的線上案例。
?
過程:
JVM死鎖導(dǎo)致線程不可用,然后會瞬間起N個線程,當然起再多也是不可用的,因為需要的對象發(fā)生死鎖,然后耗盡文件句柄導(dǎo)致外部請求也就是TCP連接無法建立產(chǎn)生拒絕服務(wù),看起來就像線程假死了一樣,不過巧合的是jstack之后就會恢復(fù)。
?
問題升級:
futex.c的bug->JVM死鎖->起更多的線程->達到線程上限->新的請求無線程可以使用->拒絕服務(wù)
?
原因:
是Linux內(nèi)核某個switch分支缺少memory barrier的正確處理,導(dǎo)致外部應(yīng)用如JVM的lock被錯誤鎖住;一般jstack連后就恢復(fù),當然你線上不能老是這樣是不是,必須徹底解決這個問題。
?
解決辦法:
方法一:上層解決替換中間件類庫 ,比如httpclient的(前提是你是由此觸發(fā)的)。
方法二:下沉解決方案前面已經(jīng)說了給Linux內(nèi)核打patch或者升級內(nèi)核到比較穩(wěn)定的新版本。
?
內(nèi)存屏障(英語:Memory barrier),也稱內(nèi)存柵欄,內(nèi)存柵障,屏障指令等,是一類同步屏障指令,是CPU或編譯器在對內(nèi)存隨機訪問的操作中的一個同步點,使得此點之前的所有讀寫操作都執(zhí)行后才可以開始執(zhí)行此點之后的操作。 大多數(shù)現(xiàn)代計算機為了提高性能而采取亂序執(zhí)行,這使得內(nèi)存屏障成為必須。
關(guān)于內(nèi)存屏障參考:User-space RCU: Memory-barrier menagerie?https://lwn.net/Articles/573436/
?
先看linux-2.6.33.1的代碼\linux-2.6.33.1\linux-2.6.33.1\kernel\futex.c
然后再看Linus的修復(fù)記錄:?
https://github.com/torvalds/linux/commit/76835b0ebf8a7fe85beb03c75121419a7dec52f0
很清楚的看到這個switch被加了default,以前是沒有這個所以導(dǎo)致死鎖的。
/** Take a reference to the resource addressed by a key.* Can be called while holding spinlocks.**/
static void get_futex_key_refs(union futex_key *key)
{if (!key->both.ptr)return;switch (key->both.offset & (FUT_OFF_INODE|FUT_OFF_MMSHARED)) {case FUT_OFF_INODE:ihold(key->shared.inode); /* implies MB (B) */break;case FUT_OFF_MMSHARED:futex_get_mm(key); /* implies MB (B) */break;default:smp_mb(); /* explicit MB (B) */}
}
v3.18版修復(fù)?:?
?
?
futex: Ensure get_futex_key_refs() always implies a barrierCommit b0c29f7 (futexes: Avoid taking the hb->lock if there's
nothing to wake up) changes the futex code to avoid taking a lock when
there are no waiters. This code has been subsequently fixed in commit
11d4616 (futex: revert back to the explicit waiter counting code).
Both the original commit and the fix-up rely on get_futex_key_refs() to
always imply a barrier.However, for private futexes, none of the cases in the switch statement
of get_futex_key_refs() would be hit and the function completes without
a memory barrier as required before checking the "waiters" in
futex_wake() -> hb_waiters_pending(). The consequence is a race with a
thread waiting on a futex on another CPU, allowing the waker thread to
read "waiters == 0" while the waiter thread to have read "futex_val ==
locked" (in kernel).Without this fix, the problem (user space deadlocks) can be seen with
Android bionic's mutex implementation on an arm64 multi-cluster system.Signed-off-by: Catalin Marinas <catalin.marinas@arm.com>
Reported-by: Matteo Franchin <Matteo.Franchin@arm.com>
Fixes: b0c29f7 (futexes: Avoid taking the hb->lock if there's nothing to wake up)
Acked-by: Davidlohr Bueso <dave@stgolabs.net>
Tested-by: Mike Galbraith <umgwanakikbuti@gmail.com>
Cc: <stable@vger.kernel.org>
Cc: Darren Hart <dvhart@linux.intel.com>
Cc: Thomas Gleixner <tglx@linutronix.de>
Cc: Peter Zijlstra <peterz@infradead.org>
Cc: Ingo Molnar <mingo@kernel.org>
Cc: Paul E. McKenney <paulmck@linux.vnet.ibm.com>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
futex:確保get_futex_key_refs()始終隱含屏障
提交b0c29f7(futexes:如果有的話,避免使用hb-> lock沒有什么可以喚醒的)
更改futex代碼以避免在什么時候鎖定沒有waiter。
此代碼隨后在提交中得到修復(fù)11d4616(futex:恢復(fù)顯式waiter計數(shù)代碼)。
原始提交和修復(fù)都依賴于get_futex_key_refs()總是意味著一個障礙。
但是,對于私有futexes,switch語句中沒有任何一種情況
將觸發(fā)get_futex_key_refs()并且函數(shù)完成
檢查“waiter”之前需要的內(nèi)存屏障futex_wake() - > hb_waiters_pending()。
結(jié)果是一場比賽,線程在另一個CPU上的futex上等待,允許waker線程讀取“waiters == 0”,而waiter線程讀取“futex_val ==鎖定“(在內(nèi)核中)。
如果沒有此修復(fù)程序,可以看到問題(用戶空間死鎖)在arm64多集群系統(tǒng)上實現(xiàn)Android bionic的互斥鎖。
?
下面是這個問題最初的發(fā)現(xiàn)和修復(fù)的討論,是ARM公司的人員發(fā)現(xiàn)的。
?https://lore.kernel.org/patchwork/patch/508701/
?參考知乎上關(guān)于這個問題的討論,類似的情況:
https://www.zhihu.com/search?type=content&q=jvm%E5%81%87%E6%AD%BB
?
https://ma.ttias.be/linux-futex_wait-bug/
?
想自己看看內(nèi)核源碼可以去:
https://mirrors.edge.kernel.org/pub/linux/kernel/
http://mirrors.163.com/kernel/linux/kernel/
?
?
總結(jié)
以上是生活随笔為你收集整理的Java线上问题排障:Linux内核bug引发JVM死锁导致线程假死的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Flink 基本原理与生产实践分享【入门
- 下一篇: 不同版本浏览器前端标准兼容性对照表以及C