首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 程式設計入門

JNI 調用 JVM 崩潰 EXCEPTION_ACCESS_VIOLATION 深度排查指南

     編輯:Java 程式設計入門

JNI (Java Native Interface) 調用導致 JVM 進程崩潰 (EXCEPTION_ACCESS_VIOLATION 0xc0000005) 是最棘手的問題之一,錯誤發生在 native 代碼但表現為 JVM 崩潰,且堆棧往往指向 JVM 內部 (jvm.dll) 而非開發者代碼。本文通過實戰崩潰日誌分析,系統講解排查思路、常見根因與防禦性編程規範。

崩潰日誌關鍵信息解讀


# A fatal error has been detected by the Java Runtime Environment:
# EXCEPTION_ACCESS_VIOLATION (0xc0000005) at pc=0x6d91c1db, pid=3544, tid=5408
# JRE version: 6.0_21-b06
# Java VM: Java HotSpot(TM) Client VM (17.0-b16 mixed mode, sharing windows-x86)
# Problematic frame:
# V  [jvm.dll+0x11c1db]

關鍵字段含義

核心線索

  1. Java 棧顯示:崩潰發生在 PrintStream.println → String.getChars → System.arraycopy,即**字符串輸出操作**
  2. 讀取地址 0x9:極接近 NULL (0x0),典型**空指針解引用**
  3. 觸發點:JNITransObject.main 第 29 行附近

根因分析:為什麼在 Java 打印時崩潰?

崩潰堆棧在 Java 端,但**根因在先前執行的 JNI 代碼**已破壞了 JVM 內部狀態:

最可能的 JNI 錯誤類型

  1. JNI 方法簽名不匹配:返回類型聲明為 TestJNI[],但 native 代碼返回錯誤類型或未正確構造數組
  2. 局部引用洩漏/過期引用使用:native 代碼創建對象後未正確管理局部/全局引用,導致 GC 移動對象後引用失效
  3. native 端 printf/cout 破壞標準輸出流:C++ std::cout 或 C printf 直接操作文件描述符 1,與 Java System.out 共享底層句柄,導致流狀態不一致
  4. 釋放已釋放/未分配內存:free() 重複釋放、訪問已 DeleteLocalRef 的對象
  5. 調用已銷毀對象方法:對象在 native 端被釋放,Java 端仍持有引用並調用方法

排查步驟實戰

1. 獲取 hs_err_pid.log 完整文件

崩潰時 JVM 在工作目錄生成 hs_err_pid<pid>.log,包含完整寄存器、棧內存、模塊列表、環境變量。

2. 定位 native 代碼對應行

  • 編譯 native 代碼時生成符號表 (-g)
  • 用 addr2line -e JNITransObject.dll 0xoffset 映射地址到源碼行
  • 或 Visual Studio / WinDbg 加載 dump 分析

3. 檢查 JNI 關鍵點


// ✅ 正確:檢查異常
jobjectArray result = (*env)->NewObjectArray(env, count, cls, NULL);
if ((*env)->ExceptionCheck(env)) return NULL;

// ✅ 正確:構造對象數組
for (int i = 0; i < count; i++) {
    jobject obj = (*env)->NewObject(env, cls, constructor, ...);
    (*env)->SetObjectArrayElement(env, result, i, obj);
    // 必須檢查每次 JNI 調用後的異常
}

// ❌ 錯誤:直接操作標準輸出
printf("debug: %s\n", str); // 破壞 System.out 內部緩衝區

// ✅ 正確:用 Java 端打印或 JNI 回調 Java 方法
jclass sysClass = (*env)->FindClass(env, "java/lang/System");
jfieldID outField = (*env)->GetStaticFieldID(env, sysClass, "out", "Ljava/io/PrintStream;");
jobject out = (*env)->GetStaticObjectField(env, sysClass, outField);
jclass psClass = (*env)->GetObjectClass(env, out);
jmethodID println = (*env)->GetMethodID(env, psClass, "println", "(Ljava/lang/String;)V");
(*env)->CallVoidMethod(env, out, println, (*env)->NewStringUTF(env, "debug msg"));

4. 啟用 JNI 關鍵檢查


# 啟動參數添加
-Xcheck:jni
# 檢查:參數類型、引用有效性、數組邊界、修飾符訪問、關鍵區域嵌套
# 會顯著降低性能,僅用於開發/測試環境

防禦性 JNI 編程規範

  1. 所有 JNI 調用後檢查 ExceptionCheck(),拋出 Java 異常並提前返回
  2. 嚴格區分局部/全局引用:NewGlobalRef 跨調用保持,DeleteGlobalRef 及時釋放
  3. 禁止 native 端直接使用 stdout/stderr,統一回調 Java 打印方法
  4. 數組操作用 Get/Release 橋接:GetIntArrayElements / ReleaseIntArrayElements,避免直接操作堆內存
  5. 關鍵區域:GetPrimitiveArrayCritical / ReleasePrimitiveArrayCritical 內**禁止調用任何 JNI 函數、分配內存、阻塞調用**
  6. 編譯開啟:-Wall -Wextra -Werror -fno-omit-frame-pointer
  7. 靜態分析:Clang Static Analyzer、Cppcheck 掃描 JNI 代碼

調試工具鏈

字段含義本例數值
Exception異常類型EXCEPTION_ACCESS_VIOLATION (0xc0000005) - 內存訪問違規
pc程序計數器 (崩潰指令地址)0x6d91c1db
Problematic frame崩潰幀類型V = VM 內部代碼 (jvm.dll)
siginfo信號詳細信息reading address 0x00000009 (嘗試讀取接近 NULL 的地址)
Java framesJava 層調用棧System.arraycopy → String.getChars → BufferedWriter.write → PrintStream.println → JNITransObject.main
工具用途
hs_err_pid.log崩潰現場第一現場
-Xcheck:jni運行時 JNI 規範檢查
Visual Studio DebuggerWindows 下混合模式調試 (Java + C++)
WinDbg + SOS生產環境 Dump 事後分析
Java Flight Recorder (JFR)JDK 11+ 事件記錄,含 JNI 調用
Valgrind / AddressSanitizerLinux 下內存錯誤檢測 (需編譯插桩)

總結

EXCEPTION_ACCESS_VIOLATION 在 JNI 調用中 90%+ 由 **native 代碼破壞 JVM 內部狀態** 引起,而非 JVM 自身 Bug。核心排查路徑:hs_err 日誌 → Java 棧定位觸發操作 → 檢查先前 JNI 代碼的引用管理、輸出流干擾、簽名匹配。落地 -Xcheck:jni、禁止 native 直接輸出、嚴格引用生命周期管理,可消絕大多數此類崩潰。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved