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

如何使用Android Profiler追踪JNI中的内存泄漏

用Android Profiler排查JNI层内存泄漏

一、启动Android Profiler追踪内存

  • 打开Android Studio,连接目标设备/模拟器,加载你的NDK项目
  • 切换到底部的Profiler面板,选中你的应用进程
  • 进入Memory模块,点击左上角的红色录制按钮开始记录
  • 反复触发nativeExtractLiveFeature函数的调用(比如循环执行或多次操作UI)
  • 完成操作后点击停止录制,生成内存快照

二、定位Native内存泄漏

在Memory快照中重点关注Native Memory区域:

  • 观察内存趋势:如果每次调用JNI函数后Native内存持续上涨且不回收,说明存在泄漏
  • 切换到Native标签页,查看具体的内存分配记录
  • 通过搜索关键词(如cv::Mat、Live、FaceFeature或你的JNI函数名)筛选出相关的分配条目,定位可疑的内存占用点

三、结合JNI代码排查潜在泄漏点

针对你提供的JNI函数代码:

JNIEXPORT jfloat JNICALL
FACE_ENGINE_METHOD(nativeExtractLiveFeature)(JNIEnv *env, jobject instance,
        jobject bmp, jint left, jint top, jint right, jint bottom,
        jfloatArray landmarksX, jfloatArray landmarksY, jfloatArray features)
{
    cv::Mat matBGR;
    ConvertBitmap2Mat(env, bmp, matBGR);
    if( matBGR.empty() || matBGR.cols == 0 || matBGR.rows == 0)
        return 0.0f;

    LOG_DEBUG("Frame Size = (%d, %d)", matBGR.cols, matBGR.rows);
    LOG_DEBUG("Face Rect = (%d, %d, %d, %d)", left, top, right, bottom);

    cv::Rect rtFace;
    rtFace.x = max(0, left);
    rtFace.y = max(0, top);
    rtFace.width = min(matBGR.cols, right) - rtFace.x;
    rtFace.height = min(matBGR.rows, bottom) - rtFace.y;

    Live *pLive = Live::getInstance();
    float confidence = pLive->Detect(matBGR, rtFace);

    LOG_DEBUG("Face Live Conf = %f", confidence);

    float posLMX[5];
    float posLMY[5];
    jfloat* tmpLandX = env->GetFloatArrayElements(landmarksX, NULL);
    jfloat* tmpLandY = env->GetFloatArrayElements(landmarksY, NULL);
    for(int i = 0; i < 5; i++){
        posLMX[i] = tmpLandX[i];
        posLMY[i] = tmpLandY[i];
        LOG_DEBUG("Landmark (x, y) = (%.2f, %.2f)", posLMX[i], posLMY[i]);
    }

    float feat[128];
    FaceFeature *pFF = FaceFeature::getInstance();

    pFF->GetFeatures(matBGR, posLMX, posLMY, feat);

    LOG_DEBUG("It was finished to extract the feature");

    matBGR.release();

    env->SetFloatArrayRegion(features, 0, 128, (jfloat*)feat);

    env->ReleaseFloatArrayElements(landmarksX, tmpLandX, 0);
    env->ReleaseFloatArrayElements(landmarksY, tmpLandY, 0);

    return confidence;
}

重点检查以下几个方面:

  1. OpenCV内存处理
    • 确认ConvertBitmap2Mat函数内部是否正确管理内存:如果函数中创建了临时cv::Mat或动态分配内存,是否有遗漏释放?
    • 虽然你调用了matBGR.release(),但如果ConvertBitmap2Mat存在隐式内存分配(比如将Bitmap数据复制到堆上),需确保这部分内存被正确回收
  2. 单例对象的内存泄漏
    • Live和FaceFeature是单例模式,检查其Detect和GetFeatures方法:是否每次调用都分配了堆内存(如new创建对象)但未释放?
    • 查看单例内部是否存在全局容器(如std::vector)持续添加元素却不清理,导致内存占用不断增长
  3. JNI数组操作的边界情况
    • 当前代码中GetFloatArrayElements在matBGR非空的分支里,提前返回时不会触发,但如果后续修改代码增加了其他提前返回路径,需确保ReleaseFloatArrayElements被执行到
  4. 隐性资源泄漏
    • 检查Live和FaceFeature是否初始化了全局资源(如OpenCV检测器、特征提取模型),这些资源是否在单例生命周期结束时正确释放?若单例静态存在且每次调用都创建新资源,会导致持续泄漏

四、验证泄漏点的方法

  • 在可疑的内存分配/释放位置添加日志,打印内存地址,确认是否存在重复分配但未释放的对象
  • 配合NDK的ndk-stack工具,结合Profiler的内存快照定位到具体的Native代码行
  • 通过注释部分代码(如先注释pFF->GetFeatures调用),观察内存是否停止增长,逐步缩小泄漏范围

内容的提问来源于stack exchange,提问作者fresh

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.07 06:05:02