You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何通过C++启动器捕获Java异常与JVM崩溃退出信息?

如何通过C++启动器捕获Java异常与JVM崩溃退出信息?

看起来你遇到的核心问题是Java普通异常捕获JVM崩溃检测的混淆,还有hs_err日志不生成的问题——我之前做C++启动Java Swing程序时也踩过几乎一模一样的坑,咱们一步步拆解解决。

先搞懂核心区别:Java异常 ≠ JVM崩溃

你现在用的env->ExceptionOccurred()只能捕获Java层面的可控异常(比如空指针、数组越界这种能在Java代码里try-catch的错误),但JVM崩溃是JVM本身的致命错误(比如JNI内存访问违规、JVM内部bug、堆溢出导致的JVM终止):

  • 当JVM崩溃时,它会直接终止整个进程(因为你是在C++进程里加载jvm.dll,JVM和启动器是同一个进程),根本走不到你后面的异常检查代码;
  • 你的C++启动器会因为进程突然终止,跳过后续逻辑直接退出,所以你看不到任何崩溃提示,也不会触发MessageBox

第一步:先让hs_err_pid.log正常生成

你看不到hs_err日志,大概率是两个原因:

  1. JVM默认只有在异常终止时才生成日志,但你手动调用jvm->DestroyJavaVM()会被JVM判定为正常退出;
  2. JVM的日志生成路径不是你预期的位置,或者没配置强制生成参数。

解决方案:给JVM添加启动参数

在你的JVM选项列表里加上这两个参数,指定日志路径和崩溃提示:

// 新增:指定hs_err日志生成路径,%p会自动替换为进程PID
options.push_back({ (char*)"-XX:ErrorFile=./hs_err_pid%p.log", NULL });
// 新增:JVM崩溃时弹出系统提示框(调试阶段用,上线可以去掉)
options.push_back({ (char*)"-XX:+ShowMessageBoxOnError", NULL });
// 可选:强制JVM在OOM时直接崩溃生成日志
options.push_back({ (char*)"-XX:+CrashOnOutOfMemoryError", NULL });

另外,记得把C++启动器的工作目录设置到你的程序根目录,否则相对路径的日志会生成到系统默认目录:

// 在获取appDir之后添加这行
SetCurrentDirectoryW(appDir.c_str());

第二步:在C++启动器中捕获JVM崩溃

因为JVM和C启动器是同一个进程,JVM崩溃会直接导致C进程崩溃,我们可以用Windows的**结构化异常处理(SEH)**来捕获这个崩溃。

代码修改:用__try/__except包裹Java方法调用

把你调用Java main方法的代码块改成这样:

if (mainMethod) {
    jobjectArray args = env->NewObjectArray(0, env->FindClass("java/lang/String"), NULL);
    
    // 用SEH包裹Java代码执行逻辑,捕获JVM崩溃的结构化异常
    __try {
        env->CallStaticVoidMethod(mainClass, mainMethod, args);
    }
    __except(EXCEPTION_EXECUTE_HANDLER) {
        // 这里捕获到JVM崩溃(比如0xC0000005内存访问违规)
        DWORD errCode = GetExceptionCode();
        std::wstring errMsg = L"检测到JVM崩溃!异常代码:0x" + std::to_wstring(errCode);
        MessageBoxW(NULL, errMsg.c_str(), L"崩溃提示", MB_OK | MB_ICONERROR);
        
        // 这里可以做自定义崩溃处理:比如记录日志、上传崩溃信息等
    }

    // 这里依然可以处理Java层面的普通异常
    if (env->ExceptionOccurred()) {
        env->ExceptionDescribe();  
        env->ExceptionClear(); 
        MessageBoxW(NULL, L"Java代码抛出未捕获异常!", L"错误", MB_OK | MB_ICONERROR);
    }
}

这样:

  • Java层面的普通异常会被env->ExceptionOccurred()捕获;
  • JVM崩溃会被__try/__except捕获,你可以在这里做自定义的崩溃处理。

更可靠的方案:把Java程序作为独立进程启动

如果不想让JVM崩溃连累C++启动器进程,推荐你改用CreateProcessW启动javaw.exe来运行你的jar,这样JVM和启动器是两个独立进程:

  • JVM崩溃只会终止Java进程,C++启动器可以全程监控;
  • hs_err日志会自动生成,不需要额外配置;
  • 你可以通过进程退出码判断是否是异常崩溃。

示例代码片段

// 替换原来加载jvm.dll并调用CreateVM的逻辑
std::wstring javaExePath = appDir + L"\\runtime\\bin\\javaw.exe";
std::wstring cmdLine = L" -jar \"" + jarPath + L"\"";

STARTUPINFOW si = { sizeof(STARTUPINFOW) };
PROCESS_INFORMATION pi;

// 启动Java进程
if (CreateProcessW(
    javaExePath.c_str(), 
    (LPWSTR)cmdLine.c_str(), 
    NULL, NULL, FALSE, 0, NULL, 
    appDir.c_str(), // 设置Java进程的工作目录
    &si, &pi
)) {
    // 等待Java进程退出
    WaitForSingleObject(pi.hProcess, INFINITE);
    
    // 获取进程退出码
    DWORD exitCode;
    GetExitCodeProcess(pi.hProcess, &exitCode);
    
    // 正常退出码一般是0或1,异常崩溃的退出码是比如0xC0000005
    if (exitCode != 0 && exitCode != 1) {
        MessageBoxW(NULL, L"Java进程异常崩溃!", L"错误", MB_OK | MB_ICONERROR);
        // 这里可以读取hs_err日志做后续处理
    }
    
    // 关闭进程句柄
    CloseHandle(pi.hProcess);
    CloseHandle(pi.hThread);
} else {
    MessageBoxW(NULL, L"启动Java进程失败!", L"错误", MB_OK | MB_ICONERROR);
}

这个方案的优势是隔离性强,上线后更稳定,不用担心JVM崩溃把启动器带崩。


调试小技巧

你可以在Java代码里主动触发JVM崩溃来测试你的捕获逻辑:

// 方式1:用Runtime.halt强制终止JVM(会生成hs_err日志)
Runtime.getRuntime().halt(1);
// 方式2:JNI调用不存在的库(触发JVM崩溃)
System.loadLibrary("不存在的库");

按照上面的步骤修改,你应该就能正常捕获Java异常、检测JVM崩溃,并且生成hs_err日志了!

火山引擎 最新活动