Android JNI二维矩阵转置实现及内存管理规范咨询
问题背景
我正在通过Android Studio学习Android原生开发,希望掌握原生应用开发的最佳实践与注意事项。出于学习目的,我尝试实现了一个native矩阵转置函数,虽能得到正确转置结果,但不确定内存管理与释放调用是否规范。
Java层native函数声明
private native int[][] transpose(int[][] arr, int row, int column);
现有C++ JNI实现代码
extern "C" JNIEXPORT jobjectArray JNICALL Java_com_example_nativeapp_MainActivity_transpose( JNIEnv *env, jobject, jobjectArray arr, jint row, jint column) { jclass jintArrayClass = env->FindClass("[I"); jint **row_elements = new jint*[row]; for (jsize i = 0; i < row; ++i) { auto current_row = (jintArray) env->GetObjectArrayElement(arr, i); row_elements[i] = env->GetIntArrayElements(current_row, nullptr); // Line Q1 // env->ReleaseIntArrayElements(current_row, row_elements[i], JNI_ABORT); } jint **transposed_row_elements = new jint*[column]; for (jsize i = 0; i < column; ++i) { transposed_row_elements[i] = new jint[row]; for (jsize j = 0; j < row; ++j) { transposed_row_elements[i][j] = row_elements[j][i]; } } jobjectArray transposed = env->NewObjectArray(column, jintArrayClass , nullptr); for (jsize i = 0; i < column; ++i) { // Line Q2 jintArray curr_row = env->NewIntArray(row); env->SetIntArrayRegion(curr_row, 0, row, transposed_row_elements[i]); env->SetObjectArrayElement(transposed, i, curr_row); } for (jsize i = 0; i < column; ++i) { delete [] transposed_row_elements[i]; } delete [] transposed_row_elements; delete [] row_elements; return transposed; }
疑问点
- 该实现中JNIEnv的使用是否存在错误?
- 启用Line Q1的
ReleaseIntArrayElements后结果错误,但规范要求调用GetXXX后需调用ReleaseXXX,该如何正确处理? - 是否需要释放Line Q2中的
curr_row? - 在Android原生开发中,用
new分配的数组是否必须用delete释放,尤其是传入JNIEnv方法的堆分配数组?
解答与优化实现
疑问解答
JNIEnv使用是否有误
目前代码中JNIEnv的调用没有语法错误,但存在内存管理疏漏:获取的jintArray(current_row)没有调用DeleteLocalRef释放,会导致局部引用泄漏;另外FindClass返回的jintArrayClass也应该在使用完后释放局部引用。ReleaseIntArrayElements调用问题
启用Q1后结果错误,是因为你在还没使用row_elements[i]的内容就调用了ReleaseIntArrayElements,释放后内存可能被回收或覆盖,后续读取row_elements[j][i]时就会拿到错误数据。正确做法是:在完全用完所有row_elements中的数据后,再统一调用ReleaseIntArrayElements释放每个数组元素。是否需要释放curr_row
不需要。当你调用SetObjectArrayElement把curr_row存入transposed对象数组后,JNI会自动管理这个引用的生命周期——返回transposed给Java层后,Java的垃圾回收机制会负责回收对应的数组内存。如果手动调用DeleteLocalRef,反而会导致后续访问出错。new分配的数组是否必须delete
是的。JNIEnv的方法不会管理你通过C++标准库new分配的内存,这些内存属于原生堆,必须手动用delete/delete[]释放,否则会造成内存泄漏。比如代码中的row_elements、transposed_row_elements及其内部数组,都需要正确释放。
规范优化后的实现代码
extern "C" JNIEXPORT jobjectArray JNICALL Java_com_example_nativeapp_MainActivity_transpose( JNIEnv *env, jobject, jobjectArray arr, jint row, jint column) { jclass jintArrayClass = env->FindClass("[I"); if (jintArrayClass == nullptr) { return nullptr; // 处理类查找失败的情况 } jint **row_elements = new jint*[row]; if (row_elements == nullptr) { env->DeleteLocalRef(jintArrayClass); return nullptr; } // 读取原矩阵数据,暂存指针 for (jsize i = 0; i < row; ++i) { jintArray current_row = static_cast<jintArray>(env->GetObjectArrayElement(arr, i)); if (current_row == nullptr) { // 出错时先释放已分配的资源 for (jsize j = 0; j < i; ++j) { jintArray prev_row = static_cast<jintArray>(env->GetObjectArrayElement(arr, j)); env->ReleaseIntArrayElements(prev_row, row_elements[j], JNI_ABORT); env->DeleteLocalRef(prev_row); } delete[] row_elements; env->DeleteLocalRef(jintArrayClass); return nullptr; } row_elements[i] = env->GetIntArrayElements(current_row, nullptr); env->DeleteLocalRef(current_row); // 释放局部引用 } // 分配转置矩阵的原生内存 jint **transposed_row_elements = new jint*[column]; if (transposed_row_elements == nullptr) { // 出错清理 for (jsize i = 0; i < row; ++i) { jintArray current_row = static_cast<jintArray>(env->GetObjectArrayElement(arr, i)); env->ReleaseIntArrayElements(current_row, row_elements[i], JNI_ABORT); env->DeleteLocalRef(current_row); } delete[] row_elements; env->DeleteLocalRef(jintArrayClass); return nullptr; } for (jsize i = 0; i < column; ++i) { transposed_row_elements[i] = new jint[row]; if (transposed_row_elements[i] == nullptr) { // 清理已分配的转置内存 for (jsize j = 0; j < i; ++j) { delete[] transposed_row_elements[j]; } delete[] transposed_row_elements; // 清理原矩阵资源 for (jsize j = 0; j < row; ++j) { jintArray current_row = static_cast<jintArray>(env->GetObjectArrayElement(arr, j)); env->ReleaseIntArrayElements(current_row, row_elements[j], JNI_ABORT); env->DeleteLocalRef(current_row); } delete[] row_elements; env->DeleteLocalRef(jintArrayClass); return nullptr; } // 执行转置 for (jsize j = 0; j < row; ++j) { transposed_row_elements[i][j] = row_elements[j][i]; } } // 创建返回的二维数组对象 jobjectArray transposed = env->NewObjectArray(column, jintArrayClass, nullptr); if (transposed == nullptr) { // 清理所有资源 for (jsize i = 0; i < column; ++i) { delete[] transposed_row_elements[i]; } delete[] transposed_row_elements; for (jsize j = 0; j < row; ++j) { jintArray current_row = static_cast<jintArray>(env->GetObjectArrayElement(arr, j)); env->ReleaseIntArrayElements(current_row, row_elements[j], JNI_ABORT); env->DeleteLocalRef(current_row); } delete[] row_elements; env->DeleteLocalRef(jintArrayClass); return nullptr; } // 将转置后的数据写入JNI数组 for (jsize i = 0; i < column; ++i) { jintArray curr_row = env->NewIntArray(row); if (curr_row == nullptr) { // 出错清理 for (jsize j = 0; j < column; ++j) { jintArray prev_row = static_cast<jintArray>(env->GetObjectArrayElement(transposed, j)); if (prev_row != nullptr) { env->DeleteLocalRef(prev_row); } } env->DeleteLocalRef(transposed); for (jsize j = 0; j < column; ++j) { delete[] transposed_row_elements[j]; } delete[] transposed_row_elements; for (jsize j = 0; j < row; ++j) { jintArray current_row = static_cast<jintArray>(env->GetObjectArrayElement(arr, j)); env->ReleaseIntArrayElements(current_row, row_elements[j], JNI_ABORT); env->DeleteLocalRef(current_row); } delete[] row_elements; env->DeleteLocalRef(jintArrayClass); return nullptr; } env->SetIntArrayRegion(curr_row, 0, row, transposed_row_elements[i]); env->SetObjectArrayElement(transposed, i, curr_row); env->DeleteLocalRef(curr_row); // 这里可以释放,因为transposed已经持有引用 } // 释放所有原生内存和JNI引用 for (jsize i = 0; i < column; ++i) { delete[] transposed_row_elements[i]; } delete[] transposed_row_elements; for (jsize i = 0; i < row; ++i) { jintArray current_row = static_cast<jintArray>(env->GetObjectArrayElement(arr, i)); env->ReleaseIntArrayElements(current_row, row_elements[i], JNI_ABORT); env->DeleteLocalRef(current_row); } delete[] row_elements; env->DeleteLocalRef(jintArrayClass); return transposed; }
关键最佳实践总结
- 局部引用管理:所有通过
GetObjectArrayElement、FindClass获取的局部引用,必须在不再使用时调用DeleteLocalRef释放,避免JNI局部引用表溢出。 - 原生内存管理:C++
new/new[]分配的内存,必须用delete/delete[]对应释放,且要注意异常/错误路径的资源清理,防止内存泄漏。 - JNI调用顺序:
GetXXXElements必须在使用完数据后再调用ReleaseXXXElements,不能提前释放;ReleaseXXXElements的第三个参数用JNI_ABORT表示不需要同步修改回Java数组(这里我们只是读取数据),用0则会同步修改。 - 错误处理:每个JNI调用都要检查返回值是否为
nullptr,出错时要逐层清理已分配的资源,避免内存泄漏或崩溃。
内容的提问来源于stack exchange,提问作者0xdeadbeef

