Android Koom 处理 app 的OOM 一些系列问题(java /native/thread leak)
本篇文檔是基于快手團隊的Koom 2.2.0 的tag 版本的使用介紹。
前期工作
VERSION_NAME=2.2.0// 引入koom 的靜態庫,這里版本2.2.0implementation "com.kuaishou.koom:koom-native-leak-static:${VERSION_NAME}"implementation "com.kuaishou.koom:koom-java-leak-static:${VERSION_NAME}"implementation "com.kuaishou.koom:koom-thread-leak-static:${VERSION_NAME}"implementation "com.kuaishou.koom:xhook-static:${VERSION_NAME}"使用快手發布koom 的靜態庫,通過源碼編譯,可能遇到一些問題,編譯不通過。
更多信息,請閱讀快手 KOOM 詳細文檔
1.JavaLeak
1.1 koom輸出java 泄漏的json 信息:
該json中包含:
-
runningInfo: app 當前進程信息,包含線程數、fd 數據等關鍵信息
-
gcPaths: 觸發gc的對象的調用鏈
-
leakObjects:泄漏對象
-
classInfos:類信息
先來看下,leakObjects:
[ { "className":"android.graphics.Bitmap", "extDetail":"1920 x 1080", "objectId":"327801464", "size":"2073600" }, { "className":"int[]", "objectId":"1972002816", "size":"455869" }, { "className":"byte[]", "objectId":"1973350400", "size":"524301" }, { "className":"char[]", "objectId":"1974407184", "size":"1048589" } ]從上面看,可知有bimap 和數組存在泄漏,但無更詳細信息。
接下來看下gcPaths中一部分信息:
{ "gcRoot":"Local variable in native code", "instanceCount":1, "leakReason":"Bitmap Size Over Threshold, 1920x1080", "path":[ { "declaredClass":"java.lang.ClassLoader", "reference":"dalvik.system.PathClassLoader.runtimeInternalObjects", "referenceType":"INSTANCE_FIELD" }, { "declaredClass":"java.lang.Object[]", "reference":"java.lang.Object[]", "referenceType":"ARRAY_ENTRY" }, { "declaredClass":"com.kwai.koom.demo.javaleak.test.LeakMaker", "reference":"com.kwai.koom.demo.javaleak.test.LeakMaker.leakMakerList", "referenceType":"STATIC_FIELD" }, { "declaredClass":"java.util.ArrayList", "reference":"java.util.ArrayList.elementData", "referenceType":"INSTANCE_FIELD" }, { "declaredClass":"java.lang.Object[]", "reference":"java.lang.Object[]", "referenceType":"ARRAY_ENTRY" }, { "declaredClass":"com.kwai.koom.demo.javaleak.test.LeakMaker", "reference":"com.kwai.koom.demo.javaleak.test.BitmapLeakMaker.uselessObjectList", "referenceType":"INSTANCE_FIELD" }, { "declaredClass":"java.util.ArrayList", "reference":"java.util.ArrayList.elementData", "referenceType":"INSTANCE_FIELD" }, { "declaredClass":"java.lang.Object[]", "reference":"java.lang.Object[]", "referenceType":"ARRAY_ENTRY" }, { "reference":"android.graphics.Bitmap", "referenceType":"instance" } ], "signature":"38ba5ba71b7599737372f965417abcf2765dbb2a" }從gc 調用鏈看出,bitmap 被LeakMaker持有,LeakMaker 被BitmapLeakMaker持有,BitmapLeakMaker被LeakMaker 中靜態leakMakerList持有,導致bitmap 一直無法被釋放。
接下來看下runningInfo 的部分信息:
{ "buildModel":"PCLM50", "currentPage":"javaleak.JavaLeakTestActivity", "deviceMemAvaliable":"3643.6367", "deviceMemTotal":"7398.6797", "dumpReason":"reason_thread_oom", "fdCount":"138", "filterInstanceTime":"1.837", "findGCPathTime":"16.967", "jvmMax":"384.0", "jvmUsed":"6.4137344", "manufacture":"OPPO", "nowTime":"2022-08-17_15-29-50_432", "pss":"125.66699mb", "rss":"161.82812mb", "sdkInt":"31", "threadCount":"725" }從上面信息,可知 線程是725個,fd 是138個,當前頁面是JavaLeakTestActivity等關鍵信息。
1.2 studio 解析hprof 文件
接下來,通過studio 解析下koom 生成的泄漏hprof 文件(sdcard/android/data/包名/files/performance/oom/memory/hrof-aly 目錄下)。
先查看下UI(framgent/activity)泄漏:
接下來看下,json 文件中bitmap 泄漏的情況:
更多hprof 文件解讀,自行百度。
2.NativeLeak:
2.1查看logcat 中輸出的native 泄漏的日志:
2022-08-09 11:21:21.987 15584-15696/com.kwai.koom.demo I/NativeLeakTestActivity: Activity: com.kwai.koom.demo.nativeleak.NativeLeakTestActivity@36fb614//.......LeakSize: 24 ByteLeakThread: .kwai.koom.demoBacktrace:#0 pc 0x1d9c libnative-leak-test.so#1 pc 0x190c libnative-leak-test.so#2 pc 0xda278 libc.so#3 pc 0x7a448 libc.so2.2 借用android ndk 工具(ndk-stack或者addr21line )來定位代碼位置。
執行addr2line的相關命令:
從上面可以看出native-leak-test.cpp 文件中93行:
c++ 與java 是很大不同,沒有gc 垃圾回收機制,在c++ 中 聽new 開辟的內存,必須手動delete 刪除。從上面代碼可見,通過new 創建了string 指針后,執行完TestContainerLeak()后并沒有delete刪除 該內存,因此造成native 泄漏。
3.ThreadLeakMonitor使用
3.1了解下Koom中線程泄漏的案例
先來解讀下Koom中線程泄漏案例的代碼:
static NOINLINE void TestThreadLeak(int64_t delay) {//這里使用的是c++ thread,使用lamba表達式方式來創建線程,在c++ 函數也是指針。std::thread test_thread([](int64_t delay) {//設置線程名稱為test_threadpthread_setname_np(pthread_self(), "test_thread");LOGI("test_thread run");// 聲明線程指針std::thread *test_thread_1;std::thread *test_thread_2;test_thread_1 = new std::thread([]() {pthread_setname_np(pthread_self(), "test_thread_1");LOGI("test_thread_1 run");});test_thread_2 = new std::thread([]() {pthread_setname_np(pthread_self(), "test_thread_2");LOGI("test_thread_2 run");});// 沉睡delay 秒時間,再調用 上面線程的detach()和join()std::this_thread::sleep_for(std::chrono::milliseconds(delay));test_thread_1->detach();LOGI("test_thread_1 detach");test_thread_2->join();LOGI("test_thread_2 join");}, delay);test_thread.detach(); }簡單來說,創建一個名為test_thread的線程,在其內部開啟兩個線程test_thread1和test_thread2 ,沉睡指定時間后,再調用它兩的detach()和join()。
接下來,看下實際的效果:
3.2 查看線程泄漏的日志
當點擊測試案例,線程test_thread 開啟線程test_thread2和test_thread_1, 沉睡10秒后再調用它兩的join()或者detach()。
先是在logcat中輸出一下日志:
監控thread 沒有執行join()或者detach()方法下,執行了pthread_exit,則記錄下泄露線程信息。
2022-08-09 09:57:13到2022-08-09 09:57:18的間隔時間是5秒,剛好是enableThreadLeakCheck(2 * 1000L, 5 * 1000L)中的泄漏時間,超過這個時間,就會上報線程泄漏信息。更多詳細日志如下所示:
當然Koom 的線程監控并不影響自身線程的邏輯,2022-08-09 09:57:13到2022-08-09 09:57:23,期間剛好沉睡10秒后,會再調用它兩的join()或者detach(),以下日志也剛好驗證。
2022-08-09 09:57:23.335 13961-26723/com.kwai.koom.demo I/ThreadLeakTest: test_thread_1 detach 2022-08-09 09:57:23.335 13961-26723/com.kwai.koom.demo I/ThreadLeakTest: test_thread_2 join。總結
以上是生活随笔為你收集整理的Android Koom 处理 app 的OOM 一些系列问题(java /native/thread leak)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 独秀日记:讲真,你的心灵需要风水师做一次
- 下一篇: java常用坐标系转换