JNI获取类所有字段时CallObjectMethod调用崩溃问题排查
问题分析与修复
核心崩溃原因
调用CallObjectMethod时崩溃,主要是以下几种常见问题导致:
1. GetMethodID获取失败(返回NULL)
getFields()是java.lang.Class的实例方法,若klass对应的类无法找到该方法(签名错误、权限问题等),GetMethodID会返回NULL,后续传入NULL的jmethodID调用CallObjectMethod必然崩溃。
修复:
添加getFields的空值检查,同时打印错误信息排查问题:
jmethodID getFields = env->GetMethodID(klass, "getFields", "()[Ljava/lang/reflect/Field;"); if (getFields == NULL) { // 打印当前类名定位问题 jmethodID getNameMethod = env->GetMethodID(klass, "getName", "()Ljava/lang/String;"); if (getNameMethod != NULL) { jstring nameStr = (jstring)env->CallObjectMethod(klass, getNameMethod); const char* className = env->GetStringUTFChars(nameStr, NULL); std::cerr << "Failed to get getFields method for class: " << className << std::endl; env->ReleaseStringUTFChars(nameStr, className); env->DeleteLocalRef(nameStr); } return; }
2. classes中存储的jclass无效(已被GC回收)
JNI的jclass默认是局部引用,若classes缓存的是局部引用,跨JNI调用或线程切换后引用会被VM回收,导致klass成为无效指针,调用方法时崩溃。
修复:
将获取到的jclass转为全局引用再存入缓存:
jclass getClass(std::string className) { if (classes.contains(className)) return classes.at(className); jclass localKlass = env->FindClass(className.c_str()); if (localKlass == NULL) { std::cerr << "FindClass failed for: " << className << std::endl; return NULL; } // 转为全局引用,避免被GC回收 jclass globalKlass = (jclass)env->NewGlobalRef(localKlass); env->DeleteLocalRef(localKlass); classes[className] = globalKlass; return globalKlass; }
注意:程序退出时需遍历classes,调用DeleteGlobalRef释放全局引用,避免内存泄漏。
3. JNI环境env无效或线程不匹配
JNI环境是线程绑定的,若env来自其他线程或未正确初始化,调用JNI方法会崩溃。
修复:
- 确保
env是当前线程有效的JNI环境(比如通过AttachCurrentThread获取)。 - 调用JNI方法前添加
env空值检查:
if (env == NULL) { std::cerr << "JNI environment is invalid!" << std::endl; return; }
4. 局部引用未及时释放(隐性问题)
大量未释放的JNI局部引用会导致引用表溢出,间接引发崩溃。
修复:
使用完局部引用后调用DeleteLocalRef释放,比如遍历字段时的field、fieldName等对象。
完整修复后的代码示例
#include <iostream> #include <unordered_map> #include <string> std::unordered_map<std::string, jclass> classes; JNIEnv* env; // 确保此env为当前线程有效 void getAllFields(std::string className) { if (env == NULL) { std::cerr << "JNI environment is invalid!" << std::endl; return; } jclass klass = getClass(className); if (klass == NULL) { std::cerr << "Class not found: " << className << std::endl; return; } jmethodID getFields = env->GetMethodID(klass, "getFields", "()[Ljava/lang/reflect/Field;"); if (getFields == NULL) { jmethodID getNameMethod = env->GetMethodID(klass, "getName", "()Ljava/lang/String;"); if (getNameMethod != NULL) { jstring nameStr = (jstring)env->CallObjectMethod(klass, getNameMethod); const char* className = env->GetStringUTFChars(nameStr, NULL); std::cerr << "Failed to get getFields method for class: " << className << std::endl; env->ReleaseStringUTFChars(nameStr, className); env->DeleteLocalRef(nameStr); } return; } jobjectArray fieldsList = (jobjectArray) env->CallObjectMethod(klass, getFields); if (fieldsList == NULL) { std::cerr << "Failed to get fields list!" << std::endl; return; } jsize len = env->GetArrayLength(fieldsList); for (jsize i = 0; i < len; i++) { jobject field = env->GetObjectArrayElement(fieldsList, i); if (field == NULL) continue; jclass fieldClass = env->GetObjectClass(field); jmethodID getName = env->GetMethodID(fieldClass, "getName", "()Ljava/lang/String;"); if (getName == NULL) { env->DeleteLocalRef(field); env->DeleteLocalRef(fieldClass); continue; } jstring fieldName = (jstring) env->CallObjectMethod(field, getName); if (fieldName == NULL) { env->DeleteLocalRef(field); env->DeleteLocalRef(fieldClass); continue; } const char *cFieldName = env->GetStringUTFChars(fieldName, 0); if (cFieldName != NULL) { std::cout << cFieldName << std::endl; env->ReleaseStringUTFChars(fieldName, cFieldName); } env->DeleteLocalRef(fieldName); env->DeleteLocalRef(field); env->DeleteLocalRef(fieldClass); } env->DeleteLocalRef(fieldsList); } jclass getClass(std::string className) { if (classes.contains(className)) return classes.at(className); jclass localKlass = env->FindClass(className.c_str()); if (localKlass == NULL) { std::cerr << "FindClass failed for: " << className << std::endl; return NULL; } jclass globalKlass = (jclass)env->NewGlobalRef(localKlass); env->DeleteLocalRef(localKlass); classes[className] = globalKlass; return globalKlass; } // 程序退出时清理全局引用 void cleanupClasses() { for (auto& pair : classes) { env->DeleteGlobalRef(pair.second); } classes.clear(); }
额外注意事项
getFields()仅能获取public字段(含父类public字段),若需获取所有字段(包括private、protected),需使用getDeclaredFields(),方法签名同样为()[Ljava/lang/reflect/Field;。- 全局引用需在程序退出时手动释放,避免内存泄漏。
内容的提问来源于stack exchange,提问作者Derek Li
相关产品推荐
相关产品推荐

