做鋼化膜網(wǎng)站長沙seo優(yōu)化
內(nèi)存泄漏介紹
什么是內(nèi)存泄漏
內(nèi)存泄漏是指程序分配了一塊內(nèi)存(通常是動態(tài)分配的堆內(nèi)存),但在不再需要這塊內(nèi)存的情況下未將其釋放。內(nèi)存泄漏會導(dǎo)致程序浪費系統(tǒng)內(nèi)存資源,持續(xù)的內(nèi)存泄漏還導(dǎo)致系統(tǒng)內(nèi)存的逐漸耗盡,最終導(dǎo)致程序或系統(tǒng)崩潰。
內(nèi)存泄漏和常駐內(nèi)存區(qū)別
常駐內(nèi)存(Resident Set)是指進程在運行期間占用的內(nèi)存大小,包括進程使用的代碼、數(shù)據(jù)和其他資源。常駐內(nèi)存是進程在運行期間一直駐留在內(nèi)存中的部分,即使在進程不活動時也不會被釋放。
常駐內(nèi)存通常不會帶來顯著的負面影響。
程序與進程里的內(nèi)存布局
下圖是源碼與 ELF(可執(zhí)行可鏈接) 文件以及運行起來后內(nèi)存布局的簡易映射關(guān)系圖。
程序中的初始化全局變量和局部靜態(tài)變量被編譯到 .data,未初始化的全局變量和局部靜態(tài)變量編譯后放在 .bss 段,代碼主體和函數(shù)主題存放在 .text 段,ELF 文件內(nèi)實際有很多段(參考《程序員的自我修養(yǎng)-鏈接,裝載與庫》第三章)。
當(dāng)程序運行時,會將 ELF 文件加載到內(nèi)存。不同的段會加載到內(nèi)存布局中的不同位置,其中 heap 這部分就是程序員手動去動態(tài)申請和釋放內(nèi)存的部分。當(dāng)程序員用 malloc 函數(shù)申請了一塊內(nèi)存,使用完之后卻沒有 free 它的時候,就會發(fā)生內(nèi)存泄漏。內(nèi)存泄漏得越多,進程中可以使用內(nèi)存的空間就越少,時間長了就會導(dǎo)致系統(tǒng)響應(yīng)慢,甚至程序崩潰。
如何“觀察”內(nèi)存泄漏是否發(fā)生?
在 Android 系統(tǒng)上通??梢杂?dumpsys meminfo 命令查看進程的內(nèi)存使用數(shù)據(jù),重復(fù) dump 后從數(shù)據(jù)的變化情況來大致判斷是否有內(nèi)存泄漏。
也可以借助python 或者其他一些工具將數(shù)據(jù)可視化方便查看數(shù)據(jù)變化趨勢。
但這只能大致的給你展示數(shù)據(jù)變化的趨勢,而非直接明白的告訴你是否發(fā)生了內(nèi)存泄漏。因此我們需要更精確的工具來檢測是否也有內(nèi)存泄漏。
常見的內(nèi)存檢測工具介紹
本節(jié)我們將依次介紹 Malloc Debug, libmemunreachable, Asan, HwASan, MTE, Heapprofd, Memcheck(Valigrind)
內(nèi)存泄漏檢測工具(https://source.android.com/docs/core/tests/debug/native-memory?hl=zh-cn)
Malloc Debug
簡介
Malloc Debug 是一種調(diào)試本機內(nèi)存問題的方法。 它可以幫助檢測內(nèi)存損壞、內(nèi)存泄漏和釋放后使用問題。
Malloc Debug 通過對常規(guī)的 allocation 函數(shù)包裝了一層來記錄和分析內(nèi)存的申請和釋放。這些函數(shù)包括:malloc, free, calloc, realloc, posix_memalign, memalign, aligned_alloc, malloc_usable_size
使用方法
運行程序前的設(shè)置
adb shell setprop libc.debug.malloc.options "\"backtrace guard leak_track backtrace_dump_on_exit backtrace_dump_prefix=/sdcard/heap"\"
adb shell setprop libc.debug.malloc.program xxx(進程名)
參數(shù)介紹:
- backtrace: 開啟堆棧記錄。
- guard: 開啟內(nèi)存越界檢測。
- leak_track: 程序在退出時,如有內(nèi)存泄漏,而不產(chǎn)生abort。
- backtrace_dump_on_exit: 程序退出時dump堆棧和內(nèi)存信息。
- backtrace_dump_prefix: dump文件存放的路徑和文件名的開頭字符。如本處生成的文件放在/sdcard/目錄下,文件名開頭為heap字樣,注意指定的路徑要有寫權(quán)限。
- libc.debug.malloc.program: 用于設(shè)置檢測的程序,不設(shè)置則檢測所有的運行的程序。
執(zhí)行待測試程序
- 離線程序
離線程序運行完成后會在 backtrace_dump_prefix 設(shè)定的路徑下存儲 dump 文件 - 在線程序
需要先停掉程序所在的進程,再重啟該進程才會生效。
由于在線程序一般不會主動退出(如 camerahalserver),需要使用命令來主動觸發(fā) dump。
命令:kill -47 xxx(進程ID),注意多次觸發(fā)新文件覆蓋之前的文件。
當(dāng)你的程序有內(nèi)存泄漏問題的話,輸出如下報告:
E malloc_debug: +++ memtest leaked block of size 48 at 0x7a4a6a42e0 (leak 1 of 2)
E malloc_debug: Backtrace at time of allocation:
E malloc_debug: #00 pc 000000000004461c /apex/com.android.runtime/lib64/bionic/libc.so (malloc+76)
E malloc_debug: #01 pc 00000000000c83e8 /apex/com.android.runtime/lib64/bionic/libc.so (__register_atfork+40)
E malloc_debug: #02 pc 000000000005460c /apex/com.android.runtime/lib64/bionic/libc.so
E malloc_debug: #03 pc 00000000000613a0 /apex/com.android.runtime/bin/linker64
E malloc_debug: #04 pc 0000000000061144 /apex/com.android.runtime/bin/linker64
E malloc_debug: #05 pc 0000000000061144 /apex/com.android.runtime/bin/linker64
E malloc_debug: #06 pc 00000000000d5f14 /apex/com.android.runtime/bin/linker64
E malloc_debug: #07 pc 00000000000d4e0c /apex/com.android.runtime/bin/linker64
E malloc_debug: #08 pc 0000000000064004 /apex/com.android.runtime/bin/linker64
E malloc_debug: +++ memtest leaked block of size 20 at 0x793a6ae9a0 (leak 2 of 2)
E malloc_debug: Backtrace at time of allocation:
E malloc_debug: #00 pc 000000000004461c /apex/com.android.runtime/lib64/bionic/libc.so (malloc+76)
E malloc_debug: #01 pc 00000000000100b8 /data/local/tmp/memtest/memtest
E malloc_debug: #02 pc 00000000000546e8 /apex/com.android.runtime/lib64/bionic/libc.so (__libc_init+104)
E malloc_debug: Dumping to file: /sdcard/heap.19748.exit.txt
注意報告中并不是所有的 leak 都是真正的內(nèi)存泄漏,有些可能是常駐內(nèi)存,開發(fā)者需要自己判斷。
還需注意dump 路徑要有寫權(quán)限
很多時候在線運行環(huán)境下so 是無符號的程序,我們需要解析 dump 文件定位代碼行號
python3 native_heapdump_viewer.py --symbols ./symboldir/ ./heap.4169.exit.txt --html > memtest4169.html
–symbols 指定的是符號庫/程序的路徑,子目錄的路徑必須要在手機上的路徑一致。比如可執(zhí)行程序在手機里的路徑是/vendor/bin/memtest,那解釋時它的帶符號的程序路徑上需要是 ./symboldir/vendor/bin/memtest
檢測出來的并不是都是泄漏,一部分是屬于常駐內(nèi)存,尤其對于在線程序,我們需要將程序運行不同的次數(shù),抓出不同的log來做對比,找出真正增長的部分。
libmemunreachable
簡介
Android 的 libmemunreachable 是一個零開銷的本地內(nèi)存泄漏檢測器。 它會在觸發(fā)內(nèi)存檢測的時候遍歷進程內(nèi)存,同時將任何不可訪問的塊報告為泄漏。
命令行方式使用
設(shè)置屬性
adb root
adb shell setprop libc.debug.malloc.program app_process
adb shell setprop wrap.[process] "\$\@“
adb shell setprop libc.debug.malloc.options backtrace=4
參數(shù)
- backtrace_size 只收集泄漏指定 size 大小的 backtrace
- backtrace_min_size=192 backtrace_max_size=320 收集泄漏 size 介于兩者之間的backtrec
重啟應(yīng)用,執(zhí)行 dumpsys -t 600 meminfo --unreachable [process].(自測沒有 dump 出預(yù)期結(jié)果)。下面是一個帶有內(nèi)存問題的輸出結(jié)果。
Unreachable memory24 bytes in 2 unreachable allocationsABI: 'arm64'24 bytes unreachable at 71d37787d0first 20 bytes of contents:71d37787d0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................71d37787e0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................24 bytes unreachable at 71d37797d0first 20 bytes of contents:71d37797d0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................71d37797e0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
添加代碼方式使用
官方提供4個接口來檢測內(nèi)存
C interface
- bool LogUnreachableMemory(bool log_contents, size_t limit)
- bool NoLeaks()
C++ interface
- bool GetUnreachableMemory(UnreachableMemoryInfo& info, size_t limit = 100)
- std::string GetUnreachableMemoryString(bool log_contents = false, size_t limit = 100)
核心函數(shù)是 GetUnreachableMemory() 其他三個函數(shù)內(nèi)部都會調(diào)用此函數(shù)。
在使用添加代碼的方式打印時,需要在編譯代碼時需要將 libmemunreachable.so 添加到動態(tài)依賴,libmemunreachable.so 文件可以在手機 /system/lib64/libmemunreachable.so 獲取。
例子:
以下是一個包含內(nèi)存泄漏的例子,在f 函數(shù)中申請了x, y 兩塊內(nèi)存,在函數(shù)返回前x 被釋放,y 賦值后沒有被釋放。
#include "./memunreachable.h"#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>using namespace android;void f(void);
void f(void) {printf("[memtest] function f\n");int* x = (int*)malloc(10 * sizeof(int));x[0] = 0;int* y = (int*)malloc(5 * sizeof(int));y[0] = 0;y[1] = 1;y[2] = 2;y[3] = 3;y[4] = 4;free(x);
}int main(void) {printf("[memtest] hello main\n");f();// C interfaceprintf("LogUnreachableMemory()\n");LogUnreachableMemory(true, 100);return 0;
}
adb log 輸出(考慮排版省去時間戳)
log 里顯示有一個 20 bytes 的內(nèi)存泄漏,20 正是5 個 int 的大小,對應(yīng)申請但沒有釋放的 y 地址的內(nèi)存。
// 新建 Collection process
31232 31231 I libmemunreachable: collecting thread info for process 31231...
31232 31231 I libmemunreachable: collection thread done
// fork 進程運行 sweeping process
31233 31233 I libmemunreachable: searching process 31231 for allocations
31233 31233 I libmemunreachable: searching done
31233 31233 I libmemunreachable: sweeping process 31231 for unreachable memory
31233 31233 I libmemunreachable: sweeping done
31233 31233 I libmemunreachable: folding related leaks
31233 31233 I libmemunreachable: folding done
// 回到 Original process 接收檢測結(jié)果
31231 31231 I libmemunreachable: unreachable memory detection done
31231 31231 E libmemunreachable: 20 bytes in 1 allocation unreachable out of 1260 bytes in 7 allocations
31231 31231 E libmemunreachable: 20 bytes unreachable at 7a03454400
31231 31231 E libmemunreachable: contents:
31231 31231 E libmemunreachable: 7a03454400: 00 00 00 00 01 00 00 00 02 00 00 00 03 00 00 00 ................
31231 31231 E libmemunreachable: 7a03454410: 04 00 00 00 ....
31231 31231 E libmemunreachable: #00 pc 000000000003e238 /apex/com.android.runtime/lib64/bionic/libc.so (malloc+84)
31231 31231 E libmemunreachable: #01 pc 00000000000100b8 /data/local/tmp/memtest/memtest_libmemunreachable
31231 31231 E libmemunreachable: #02 pc 000000000004aa48 /apex/com.android.runtime/lib64/bionic/libc.so (__libc_init+100)
調(diào)用 bool LogUnreachableMemory(bool log_contents, size_t limit) 時,log_contents 傳 true 會打印泄露地址的內(nèi)容,也就是 contents 對應(yīng)的兩行內(nèi)容。之后可以用 address2line 解析行號。
其他內(nèi)存檢測工具簡介
ASan
- Asan(AddressSanitizer) 適用于檢測內(nèi)存越界訪問、緩沖區(qū)溢出、內(nèi)存泄漏等問題。它是一個在編譯時插入的工具;
- 運行時有一定的性能開銷(約增加兩倍),代碼大小和內(nèi)存均有額外開銷;
- 需要重新編譯程序,編譯時添加 address 相關(guān)選項;
- 可用于 linux 和 android,但在 android 上逐步被 HwASan 取代;
- 需要刷與 ASan 兼容的 ROM;
- 不再受支持,即使有bug 也不會修復(fù);
HWASan
- HWASan 利用硬件特性,適用于檢測內(nèi)存錯誤,類似于 ASan,但能夠更高效地運行在一些支持硬件特性的平臺上。
- 性能開銷和 Asan 接近,但內(nèi)存占用更小;
- 需要重新編譯程序,編譯時添加 hwaddress 相關(guān)選項
- 僅適用于 Android 10 及更高版本,AArch64 硬件;
- 需要刷與 HWASan 兼容的 ROM;
MTE
- MTE(Memory Tagging Extension) 使用硬件標(biāo)簽來檢測內(nèi)存錯誤,主要專注于檢測內(nèi)存越界訪問。
- 提供了較低的性能開銷,首次具備了線上部署的可能。
- 無需重新構(gòu)建代碼來檢測堆錯誤(但需要重新構(gòu)建代碼來檢測堆棧錯誤)
- Android 系統(tǒng)在 Arm v9 上開始支持,僅適用于64位應(yīng)用/程序;
Heapprofd
- Heapprofd 是一個跟蹤給定時間段內(nèi) Android 進程的堆分配和釋放的工具。
- 可以借助 Perfetto 抓取,開發(fā)人員可以使用該工具調(diào)查內(nèi)存問題(調(diào)用棧和內(nèi)存分配)。
當(dāng)開啟連續(xù) dump 后,開發(fā)者可以查看程序結(jié)束前內(nèi)存占用是否合理,以檢查是否有潛在內(nèi)存泄漏問題?;蛘邔⒋郎y試代碼循環(huán)執(zhí)行,比較每執(zhí)行一次代碼段后內(nèi)存是否有增加,一次判斷是否有內(nèi)存泄漏。
Valgrind 中的Memcheck
- Memcheck 是 Valgrind 工具套件中的一個工具,用于檢測 C 和 C++ 程序中的內(nèi)存錯誤。
- 內(nèi)存問題檢測比較全面,但對性能影響比較大,耗時增加10x~20x,不適用對時間敏感的程序。
- 在 Ubuntu 上安裝:
sudo apt-get install valgrind
使用 memcheck 的基本方法
- 編譯程序時加上 –g 選項,編譯優(yōu)化選項建議選擇 -O1;
- 使用 Valgrind 命令運行程序:valgrind --leak-check=yes myprog arg1 arg2valgrind 使用 --tools 來指定 debug 工具,而 Memcheck 是默認工具,可以省略 --tools=memcheck 選項;
- 程序運行后輸出問題報告。
$ valgrind --leak-check=yes ./memtest_origin
==19517== Memcheck, a memory error detector
==19517== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==19517== Using Valgrind-3.13.0 and LibVEX; rerun with -h for copyright info
==19517== Command: ./memtest_origin
==19517==
[memtest] hello main
[memtest] function f
==19517==
==19517== HEAP SUMMARY:
==19517== in use at exit: 20 bytes in 1 blocks
==19517== total heap usage: 3 allocs, 2 frees, 1,084 bytes allocated
==19517==
==19517== 20 bytes in 1 blocks are definitely lost in loss record 1 of 1
==19517== at 0x4C31B0F: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==19517== by 0x1086FF: f() (memtest_origin.cc:9)
==19517== by 0x108731: main (memtest_origin.cc:16)
==19517==
==19517== LEAK SUMMARY:
==19517== definitely lost: 20 bytes in 1 blocks
==19517== indirectly lost: 0 bytes in 0 blocks
==19517== possibly lost: 0 bytes in 0 blocks
==19517== still reachable: 0 bytes in 0 blocks
==19517== suppressed: 0 bytes in 0 blocks
==19517==
==19517== For counts of detected and suppressed errors, rerun with: -v
==19517== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)
總結(jié)
本文我們介紹了內(nèi)存泄漏的概念,malloc debug 和 libmemunreachable 的使用方法,以及一些其他內(nèi)存檢測工具的簡介,下一篇我們將介紹 malloc debug 和 libmemunreachable 的工作原理。
參考鏈接
- 【內(nèi)存】Android C/C++ 內(nèi)存泄漏分析 unreachable
- 調(diào)試本地內(nèi)存使用 ?|? Android 開源項目 ?|? Android Open Source Project
- 調(diào)試和減少內(nèi)存錯誤 ?|? Android NDK ?|? Android Developers (google.cn)
- Malloc Debug (googlesource.com)
- Malloc Hooks (googlesource.com)
- libmemunreachable (googlesource.com)
- Memcheck: a memory error detector
- Heap profiler - Perfetto Tracing Docs