You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Android JNI二维矩阵转置实现及内存管理规范咨询

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方法的堆分配数组?

解答与优化实现

疑问解答

  1. JNIEnv使用是否有误
    目前代码中JNIEnv的调用没有语法错误,但存在内存管理疏漏:获取的jintArray(current_row)没有调用DeleteLocalRef释放,会导致局部引用泄漏;另外FindClass返回的jintArrayClass也应该在使用完后释放局部引用。

  2. ReleaseIntArrayElements调用问题
    启用Q1后结果错误,是因为你在还没使用row_elements[i]的内容就调用了ReleaseIntArrayElements,释放后内存可能被回收或覆盖,后续读取row_elements[j][i]时就会拿到错误数据。正确做法是:在完全用完所有row_elements中的数据后,再统一调用ReleaseIntArrayElements释放每个数组元素。

  3. 是否需要释放curr_row
    不需要。当你调用SetObjectArrayElement把curr_row存入transposed对象数组后,JNI会自动管理这个引用的生命周期——返回transposed给Java层后,Java的垃圾回收机制会负责回收对应的数组内存。如果手动调用DeleteLocalRef,反而会导致后续访问出错。

  4. 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.25 15:17:00