JNI 中錯誤的訊號處理導致 JVM 崩潰問題分析
編者按:JNI 是 Java 和 C 語言互動的主要手段,要想做好 JNI 的程式設計並不容易,需要了解 JVM 內部機理才能避免一些錯誤。本文分析 Cassandra 使用 JNI 本地庫導致 JVM 崩潰的一個案例,最後定位問題根源是訊號的錯誤處理(一些 C 程式設計人員經常會截獲訊號,做一些額外的處理),該案例提示 JNI 程式設計時不要隨意截獲訊號處理。
現象
在使用 Cassandra 時遇到執行時多個位置都有發生 crash 現象,並且沒有 hs_err
檔案生成,這裡列舉了其中一個 crash 位置:
分析
首先直接基於上面這個 crash 的 core 檔案展開分析,下面分別是對應原始碼上下文和指令上下文:
使用 GDB 除錯對應的 core 檔案,如下圖所示:
在 GDB 中進行單步除錯(GDB 除錯可以參考官方文件),配合原始碼發現 crash 的原因是傳入的 name 為 null,導致呼叫 name.split("\_")
時觸發了 SIGSEGV 訊號,直接 crash。暫時拋開這個方法傳入 name
為 null
是否有問題不論,從 JVM 執行的機制來說,這裡有個疑問,遇到一個 Null Pointer
為什麼不是丟擲 Null Pointer Exception
(簡稱 NPE)而是直接 crash 了呢?
這裡有一個知識需要普及一下:Java 層面的 NPE 主要分為兩類,一類是程式碼中主動丟擲 NPE 異常,並被 JVM 捕獲 (這裡的程式碼既可以是 Java 程式碼,也可以是 JVM 內部程式碼);另一類隱式 NPE(其原理是 JVM 內部遇到空指標訪問,會產生 SIGSEGV 訊號, 在 JVM 內部還會檢查執行時是否存在 SIGSEGV 訊號)。
帶著上面的疑問,又看了幾處其他位置的 crash,發現都是因為物件為 null 導致的 SIGSEGV,卻都沒有丟擲 NPE,而是直接 crash 了,再結合都沒有 hs_err 檔案生成的現象, hs_err 檔案生成功能位於 JVM 的 SIGSEGV 訊號處理函式中,程式碼如下:
由於 hs_err
檔案沒有產生,一個很自然的推斷:Cassandra 執行中可能篡改了或者捕獲了 SIGSEGV 訊號,並且可能做了處理,以至於 JVM 無法正常處理 SIGSEGV 訊號。
然後排查業務方是否在 Cassandra 中用到了自定義的第三方 native 庫,果然筆者所猜測的,有兩個 native 庫裡都對 SIGSEGV 訊號做了捕獲,註釋掉這些程式碼後重新跑對方的業務,crash 現象不再發生,問題(由於 Cassandra 中對 NPE 有異常處理導致 JVM 崩潰)解決。
總結
C/C++ 的元件在配合 Java 一起使用時,需要注意的一點就是不要隨意去捕獲系統訊號,特別是 SIGSEGV、SIGILL、SIGBUS 等,因為會覆蓋掉 JVM 中的訊號捕獲邏輯。附錄 這裡貼一個 demo 可以用來複現 SIGSEGV 訊號覆蓋造成的後果,有興趣的可以跑一下:
// JNITest.java
import java.util.UUID;
public class JNITest {
public static void main(String[] args) throws Exception {
System.loadLibrary("JNITest");
UUID.fromString(null);
}
}
// JNITest.c
#include <signal.h>
#include <jni.h>
JNIEXPORT
jint JNICALL JNI_OnLoad(JavaVM *jvm, void *reserved) {
signal(SIGSEGV, SIG_DFL);//如果註釋這條語句,在執行時會出現 NullPointerExcetpion 異常
return JNI_VERSION_1_8;
}
通過 GCC 編譯並執行就可以觸發相同的問題,編譯執行命令如下:
$ gcc -Wall -shared -fPIC JNITest.c -o libJNITest.so -I$JAVA_HOME/include -I$JAVA_HOME/include/linux
$ javac JNITest.java
\$ java -Xcomp -Djava.library.path=./ JNITest
後記
如果遇到相關技術問題(包括不限於畢昇 JDK),可以進入畢昇 JDK 社群查詢相關資源(點選原文進入官網),包括二進位制下載、程式碼倉庫、使用教學、安裝、學習資料等。畢昇 JDK 社群每雙週週二舉行技術例會,同時有一個技術交流群討論 GCC、LLVM、JDK 和 V8 等相關編譯技術,感興趣的同學可以新增如下微信小助手,回覆 Compiler 入群。
本文分享自微信公眾號 - openEuler(openEulercommunity)。
如有侵權,請聯絡 [email protected] 刪除。
本文參與“OSC源創計劃”,歡迎正在閱讀的你也加入,一起分享。
- 玩轉機密計算從 secGear 開始
- openEuler資源利用率提升之道06:虛擬機器混部OpenStack排程
- openGauss Cluster Manager RTO Test
- JVM 鎖 bug 導致 G1 GC 掛起問題分析和解決【畢昇JDK技術剖析 · 第 2 期】
- 手把手帶你玩轉 openEuler | openEuler 的使用
- 681名學生中選!暑期2021開啟火熱“開源之夏”!
- 手把手帶你玩轉 openEuler | 初識 openEuler
- StratoVirt 中的 PCI 裝置熱插拔實現
- 使用 NMT 和 pmap 解決 JVM 資源洩漏問題
- JNI 中錯誤的訊號處理導致 JVM 崩潰問題分析
- Java Flight Recorder - 事件機制詳解
- 畢昇 JDK 8u292、11.0.11 釋出!
- StratoVirt 中的虛擬網絡卡是如何實現的?
- openEuler結合ebpf提升ServiceMesh服務體驗的探索
- 我的openEuler社群參與之旅
- StratoVirt 的中斷處理是如何實現的?
- 看看畢昇 JDK 團隊是如何解決 JVM 中 CMS 的 Crash
- 使用 perf 解決 JDK8 小版本升級後效能下降的問題【畢昇JDK技術剖析 · 第 1 期】
- 2021年畢昇 JDK 的第一個重要更新來了
- 漏洞盒子 × openEuler | 廣邀白帽共築安全的Linux開放應用生態