如何使用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; }
重点检查以下几个方面:
- OpenCV内存处理
- 确认
ConvertBitmap2Mat函数内部是否正确管理内存:如果函数中创建了临时cv::Mat或动态分配内存,是否有遗漏释放? - 虽然你调用了
matBGR.release(),但如果ConvertBitmap2Mat存在隐式内存分配(比如将Bitmap数据复制到堆上),需确保这部分内存被正确回收
- 确认
- 单例对象的内存泄漏
Live和FaceFeature是单例模式,检查其Detect和GetFeatures方法:是否每次调用都分配了堆内存(如new创建对象)但未释放?- 查看单例内部是否存在全局容器(如
std::vector)持续添加元素却不清理,导致内存占用不断增长
- JNI数组操作的边界情况
- 当前代码中
GetFloatArrayElements在matBGR非空的分支里,提前返回时不会触发,但如果后续修改代码增加了其他提前返回路径,需确保ReleaseFloatArrayElements被执行到
- 当前代码中
- 隐性资源泄漏
- 检查
Live和FaceFeature是否初始化了全局资源(如OpenCV检测器、特征提取模型),这些资源是否在单例生命周期结束时正确释放?若单例静态存在且每次调用都创建新资源,会导致持续泄漏
- 检查
四、验证泄漏点的方法
- 在可疑的内存分配/释放位置添加日志,打印内存地址,确认是否存在重复分配但未释放的对象
- 配合NDK的
ndk-stack工具,结合Profiler的内存快照定位到具体的Native代码行 - 通过注释部分代码(如先注释
pFF->GetFeatures调用),观察内存是否停止增长,逐步缩小泄漏范围
内容的提问来源于stack exchange,提问作者fresh
相关产品推荐
相关产品推荐

