Java 21迁移后JNI CallStaticObjectMethod触发访问违例崩溃求助
Java 21迁移后JNI调用
CallStaticObjectMethod触发Access Violation崩溃问题 我们将系统从Java 8迁移至Java 21并更新JNI版本后,所有JNI调用在Java 8下正常,但调用CallStaticObjectMethod触发Access Violation异常崩溃,仅该方法出现问题,其余JNI方法运行正常。
崩溃的C++调用代码
jobject Bindings::ToSystemArchObj(JNIEnv* env, const std::string& arch) { if (env == nullptr || arch == ARCH_UNSUPPORTED) { return nullptr; } jobject jstr = env->NewStringUTF(arch.c_str()); // 已尝试将jstr类型从jobject转为jstring processArchObj = env->CallStaticObjectMethod(JniProcessInfoDTO::SystemArchClass, JniProcessInfoDTO::ofMethodId, jstr); // 该行触发访问违例并崩溃 return processArchObj; }
JNI类与方法ID初始化代码
void JniProcessInfoDTO::initClass(JNIEnv* env) { jclass const localProcessInfoDTO = env->FindClass(JavaTypes::ProcessInfoDTO.c_str()); ProcessInfoDTORef = reinterpret_cast<jclass>(env->NewGlobalRef(localProcessInfoDTO)); initFieldIDs(env); // std::string const SystemArch = "Lcom/company/mfw/model/dto/SystemArch;"; SystemArchClass = env->FindClass(JavaTypes::SystemArch.c_str()); // (Ljava/lang/String;)Lcom/company/mfw/model/dto/SystemArch; const std::string ofSign = "(" + JavaTypes::String + ")" + JavaTypes::SystemArch; //// std::string const String = "java/lang/String;"; ofMethodId = env->GetStaticMethodID(SystemArchClass, "of", ofSign.c_str()); }
Java端静态方法(SystemArch枚举)
package com.company.mfw.model.dto; public enum SystemArch { X86, X86_64, UNSUPPORTED; public static SystemArch of(String arch) { try { return SystemArch.valueOf(arch); } catch (Exception ignored) { return UNSUPPORTED; } } }
JNI加载与卸载逻辑
extern "C" { // cppcheck-suppress unusedFunction JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void*) { // This is an entry point for initialization of Java classes. // All required Java classes must exist and must be initialized here. // JNI_OnLoad must not be wrapped with Safe Environment. // JVM should be crashed in case of exceptions here. JniGlobals::JVMConnector::InitJavaVM(vm); JavaVMAttachArgs args; args.version = JNI_VERSION_21; args.name = nullptr; args.group = nullptr; JNIEnv* env = nullptr; vm->AttachCurrentThread((void**)&env, &args); JniContext::Current().JniEnv = env; JniGlobals::unmaskFPSR(); GdiplusStartup(&JniGlobals::gdiplusToken, &JniGlobals::gdiplusStartupInput, nullptr); Bindings::JniComponentDTO::initClass(env); Bindings::JniWindowDTO::initClass(env); Bindings::JniPointDTO::initClass(env); Bindings::JniDimensionDTO::initClass(env); Bindings::JniRectangleDTO::initClass(env); Bindings::JniProcessInfoDTO::initClass(env); Bindings::JniProcessStartupInfoDTO::initClass(env); Bindings::JniProcessEndInfoDTO::initClass(env); Bindings::JniKeyDTO::initClass(env); Bindings::JniNativeMouseEvent::initClass(env); Bindings::JniNativeKeyEvent::initClass(env); Bindings::JniHooksSettings::initClass(env); Bindings::WinSessionDTO::initClass(env); JniGlobals::TimeManager::GetInstance().InitJavaHandlers(env); JniGlobals::JavaObjectFactory::JavaObjectFactoryClass = env->FindClass("com/company/mfw/bridge/internal/JavaObjectsFactory"); JniGlobals::JavaObjectFactory::NewStringMethodID = env->GetStaticMethodID(JniGlobals::JavaObjectFactory::JavaObjectFactoryClass, "newString", "([B)Ljava/lang/String;"); Log::GetInstance().Init(); LOG_TRACE(L"BEGIN"); auto osInfo = JniGlobals::WindowsSystemInfo::GetInstance().ReadOsInfo(); std::string csd(osInfo.szCSDVersion); LOG_INFO(L"System info. Version: {}.{}, Build: {}, Platform ID: {}, CSD Version: {}", (int)osInfo.dwMajorVersion, (int)osInfo.dwMinorVersion, (int)osInfo.dwBuildNumber, (int)osInfo.dwPlatformId, csd.length() > 0 ? StringsConverter::StringToWstring(csd) : L"<empty>"); return JNI_VERSION_21; } // cppcheck-suppress unusedFunction JNIEXPORT void JNICALL JNI_OnUnload(JavaVM*, void*) { LOG_TRACE(L"BEGIN"); Gdiplus::GdiplusShutdown(JniGlobals::gdiplusToken); JNIEnv* env = JniContext::Current().JniEnv; Bindings::JniComponentDTO::releaseClass(env); Bindings::JniWindowDTO::releaseClass(env); Bindings::JniPointDTO::releaseClass(env); Bindings::JniDimensionDTO::releaseClass(env); Bindings::JniRectangleDTO::releaseClass(env); Bindings::JniProcessInfoDTO::releaseClass(env); Bindings::JniProcessStartupInfoDTO::releaseClass(env); Bindings::JniProcessEndInfoDTO::releaseClass(env); Bindings::JniKeyDTO::releaseClass(env); Bindings::JniNativeMouseEvent::releaseClass(env); Bindings::JniNativeKeyEvent::releaseClass(env); Bindings::JniHooksSettings::releaseClass(env); Bindings::WinSessionDTO::releaseClass(env); } } // extern "C"
补充上下文(更新2)
C++类型转换调用代码
/** * Converts a ProcessInfoPtr to a Java ProcessInfoDTO object. * * @param env The JNI environment pointer. * @param pi The ProcessInfoPtr object. * @return The corresponding Java ProcessInfoDTO object or nullptr if the environment or ProcessInfoPtr is null. */ jobject Bindings::ToJavaType(JNIEnv* env, ProcessInfoPtr pi) { if (env == nullptr || pi == nullptr) { return nullptr; // Return nullptr if environment or ProcessInfoPtr is null } jobject joProcessInfoDTO = env->AllocObject(JniProcessInfoDTO::ProcessInfoDTORef); // Allocate a new ProcessInfoDTO object env->SetIntField(joProcessInfoDTO, JniProcessInfoDTO::processPidFieldId, pi->pid); // Set process PID field env->SetIntField(joProcessInfoDTO, JniProcessInfoDTO::processParentPidFieldId, pi->parentPid); // Set process parent PID field env->SetObjectField(joProcessInfoDTO, JniProcessInfoDTO::processChildProcessFieldId, ToJavaType(env, pi->childProcesses)); // Set child processes field env->SetObjectField(joProcessInfoDTO, JniProcessInfoDTO::processCommandLineFieldId, ToJavaType(env, pi->commandLine)); // Set command line field env->SetObjectField(joProcessInfoDTO, JniProcessInfoDTO::processNameFieldId, ToJavaType(env, pi->name)); // Set process name field env->SetObjectField(joProcessInfoDTO, JniProcessInfoDTO::processOwnerFieldId, ToJavaType(env, pi->owner)); // Set process owner field env->SetIntField(joProcessInfoDTO, JniProcessInfoDTO::processResidentBytesFieldId, pi->residentBytes); // Set resident bytes field env->SetIntField(joProcessInfoDTO, JniProcessInfoDTO::processTotalBytesFieldId, pi->totalBytes); // Set total bytes field env->SetIntField(joProcessInfoDTO, JniProcessInfoDTO::processSystemMillsFieldId, pi->systemMillis); // Set system milliseconds field env->SetIntField(joProcessInfoDTO, JniProcessInfoDTO::processUserMillsFieldId, pi->userMillis); // Set user milliseconds field env->SetObjectField(joProcessInfoDTO, JniProcessInfoDTO::processArchFieldId, ToSystemArchObj(env, pi->arch)); // Set process architecture field return joProcessInfoDTO; // Return the ProcessInfoDTO object }
C++ ProcessInfo类定义
class ProcessInfo { public: ProcessInfo() : pid(-1), parentPid(0), commandLine(L""), name(L""), owner(L""), residentBytes(0), totalBytes(0), systemMillis(0), userMillis(0), arch("") { childProcesses.resize(0); }; ~ProcessInfo() = default; public: int pid; int parentPid; std::vector<int> childProcesses; std::wstring commandLine; std::wstring name; std::wstring owner; long residentBytes; long totalBytes; long systemMillis; long userMillis; std::string arch; };
Java ProcessInfoDTO类部分定义
public class ProcessInfoDTO { private static final long serialVersionUID = -212324578578245L; private int pid; private int parentPid; private String commandLine; private String name; private String owner; private int[] childProcesses; private long residentBytes; private long totalBytes; private long systemMillis; private long userMillis; private SystemArch arch; public int getPid() { return pid; } public void setPid(int pid) { this.pid = pid; } // 更多函数定义 // 更多函数定义 }
排查方向
局部引用未转为全局引用:
SystemArchClass是FindClass返回的局部引用,在JNI_OnLoad线程外使用会失效。Java 21对局部引用的生命周期管理更严格,需调用NewGlobalRef保存:// 修改initClass中的SystemArchClass初始化 jclass localSystemArch = env->FindClass(JavaTypes::SystemArch.c_str()); SystemArchClass = reinterpret_cast<jclass>(env->NewGlobalRef(localSystemArch)); env->DeleteLocalRef(localSystemArch);同时要在
releaseClass中释放全局引用:void JniProcessInfoDTO::releaseClass(JNIEnv* env) { env->DeleteGlobalRef(SystemArchClass); // 其他释放逻辑 }方法签名错误:检查
JavaTypes::String和JavaTypes::SystemArch的定义是否正确。正确的签名应该是:- String:
"Ljava/lang/String;" - SystemArch:
"Lcom/company/mfw/model/dto/SystemArch;"
确保拼接后的签名是"(Ljava/lang/String;)Lcom/company/mfw/model/dto/SystemArch;",可通过javap -s com.company.mfw.model.dto.SystemArch验证。
- String:
JNI环境有效性:调用
ToSystemArchObj时,当前线程是否已正确附着到JVM?检查env指针是否有效,可在调用前添加判断:if (env == nullptr) { // 重新附着线程获取env JavaVM* vm = JniGlobals::JVMConnector::GetJavaVM(); if (vm != nullptr) { JavaVMAttachArgs args; args.version = JNI_VERSION_21; args.name = nullptr; args.group = nullptr; jint result = vm->AttachCurrentThread((void**)&env, &args); if (result != JNI_OK) { return nullptr; } } else { return nullptr; } }字符串编码问题:Java 21对字符串编码的处理可能更严格,
NewStringUTF要求输入是UTF-8编码,确保arch字符串是合法UTF-8,可尝试转换为宽字符串后用NewString:std::wstring warch = StringsConverter::StringToWstring(arch); jstring jstr = env->NewString(warch.c_str(), warch.length());
内容的提问来源于stack exchange,提问作者Stew
相关产品推荐
相关产品推荐

