Android NDK 記憶體洩露檢測
前言
最近寫C++程式碼,老是擔心程式碼存在記憶體洩露,膽戰心驚的,Andorid中Java層程式碼記憶體洩露可以藉助leakcanary進行檢測;找了一番,找到了PC上C++上的記憶體洩露檢測庫LeakTracer,於是再找了下,找到了Android上的移植版。
首先建立一個專案,在根目錄下建立thirdparty目錄,進入該目錄,clone相關庫程式碼
1 | git clone git@github.com:lizhangqu/LeakTracer.git |
在專案src/main/cpp下建立CMakeLists.txt,內容如下
12345678910111213141516171819202122232425262728293031 | project(Test)cmake_minimum_required (VERSION 3.6)include_directories( ${PROJECT_SOURCE_DIR}/include/ ${PROJECT_SOURCE_DIR}/../../../../thirdparty/LeakTracer/libleaktracer/include/ )set(LEAKTRACER_SOURCES ${PROJECT_SOURCE_DIR}/../../../../thirdparty/LeakTracer/libleaktracer/src/AllocationHandlers.cpp #${PROJECT_SOURCE_DIR} |
建立src/main/cpp/include/native.h和src/main/cpp/native.cpp檔案
native.h
1234567891011121314151617181920212223242526272829303132333435363738 | //// Created by 李樟取 on 2017/6/4.//ifndef TEST_Hdefine TEST_Hinclude "jni.h"ifndef NELEM define NELEM(x) ((int) (sizeof(x) / sizeof((x)[0])))endififndef CLASSNAMEdefine CLASSNAME "io/github/lizhangqu/test/Test"endififdef ANDROIDinclude <android/log.h>define TAG "Test"define ALOGE(fmt, ...) __android_log_print(ANDROID_LOG_ERROR, TAG, fmt, ##__VA_ARGS__)define ALOGI(fmt, ...) __android_log_print(ANDROID_LOG_INFO, TAG, fmt, ##__VA_ARGS__)define ALOGD(fmt, ...) __android_log_print(ANDROID_LOG_DEBUG, TAG, fmt, ##__VA_ARGS__)define ALOGW(fmt, ...) __android_log_print(ANDROID_LOG_WARN, TAG, fmt, ##__VA_ARGS__)elsedefine ALOGE printfdefine ALOGI printfdefine ALOGD printfdefine ALOGW printfendifendif //TEST_H |
native.cpp
1234567891011121314151617181920212223242526272829303132333435363738394041 | void test(JNIEnv *env, jobject thiz) {}static const JNINativeMethod sMethods[] = { { const_cast<char *>("test"), const_cast<char *>("()V"), reinterpret_cast<void *>(test) },};int registerNativeMethods(JNIEnv *env, const char *className, const JNINativeMethod *methods, const int numMethods) { jclass clazz = env->FindClass(className); if (!clazz) { ALOGE("Native registration unable to find class '%s'\n", className); return JNI_FALSE; } if (env->RegisterNatives(clazz, methods, numMethods) != 0) { ALOGE("RegisterNatives failed for '%s'\n", className); env->DeleteLocalRef(clazz); return JNI_FALSE; } env->DeleteLocalRef(clazz); return JNI_TRUE;}jint JNI_OnLoad(JavaVM *vm, void *reserved) { JNIEnv *env; if (vm->GetEnv(reinterpret_cast<void **>(&env), JNI_VERSION_1_6) != JNI_OK) { return -1; } registerNativeMethods(env, CLASSNAME, sMethods, NELEM(sMethods)); return JNI_VERSION_1_6;} |
編寫test函式,簡單進行內測洩露檢測
1234567891011121314151617 | class MemoryTest {};void test(JNIEnv *env, jobject thiz) { leaktracer::MemoryTrace::GetInstance().startMonitoringAllThreads(); MemoryTest *memoryTest = new MemoryTest; leaktracer::MemoryTrace::GetInstance().stopAllMonitoring(); std::ofstream out; out.open("/sdcard/leaks.out", std::ios_base::out); if (out.is_open()) { leaktracer::MemoryTrace::GetInstance().writeLeaks(out); } else { ALOGE("Failed to write to \"leaks.out\"\n"); }} |
執行程式後呼叫test函式,將/sdcard/leaks.out pull到專案根目錄
1 | adb pull /sdcard/leaks.out |
其內容類似如下內容
12 | # LeakTracer report diff_utc_mono=1496511718.682943leak, time=135120.534718, stack=0x36fd6 0x35a90 0x359a4 0x32fea 0xc952d3d0, size=1, data=� |
藉助thirdparty/LeakTracer/helper/leak-analyze-addr2line工具還原內測洩露堆疊。
進入專案根目錄,執行leak-analyze-addr2line
1 | ./thirdparty/LeakTracer/helpers/leak-analyze-addr2line ./library/build/intermediates/cmake/debug/obj/armeabi/libtest-jni.so ./leaks.out |
在mac上,會出現一個錯誤,原因是leak-analyze-addr2line中用到了addr2line工具,而mac上如果沒有此工具,就會報錯,錯誤如下:
解決方法很簡單,將ndk目錄中的arm-linux-androideabi-addr2line拷到./thirdparty/LeakTracer/helpers/下,並重命名為addr2line,然後將./thirdparty/LeakTracer/helpers/加到環境變數中,如下程式碼
12 | cp $ANDROID_NDK_HOME/toolchains/arm-linux-androideabi-4.9/prebuilt/darwin-x86_64/bin/arm-linux-androideabi-addr2line ./thirdparty/LeakTracer/helpers/addr2lineexport PATH=$PATH:`pwd`/thirdparty/LeakTracer/helpers |
$ANDROID_NDK_HOME表示ndk的根目錄。
之後再次呼叫命令
1 | ./thirdparty/LeakTracer/helpers/leak-analyze-addr2line ./library/build/intermediates/cmake/debug/obj/armeabi/libtest-jni.so ./leaks.out |
這時候輸出如下
native.cpp:15行出現洩露,找到15行對應的程式碼,即如下程式碼出現洩露
1 | MemoryTest *memoryTest = new MemoryTest; |
加入delete程式碼,再跑一次
12 | MemoryTest *memoryTest = new MemoryTest;delete memoryTest; |
輸出如下
發現之前的洩露不見了
最後,值得注意的是,要想使用LeakTracer需要保留so足夠多的debug資訊,否則可能不能正常檢測。