
鸿蒙5.0版开发:分析Resource Leak(资源泄漏)
本文将分别介绍资源泄漏检测能力、泄漏问题定位分析思路,以及具体的案例分析。
分析Resource Leak(资源泄漏)
资源泄漏是指句柄/线程/内存等资源超过系统设定上限,部分资源甚至失去了管控能力,此时系统可能出现卡死/重启等异常情况。LeakDetector模块提供资源泄漏检测、判决、维测日志抓取、日志上报的能力,为开发者提供详细的维测日志以辅助故障定位。
本文将分别介绍资源泄漏检测能力、泄漏问题定位分析思路,以及具体的案例分析。
资源泄漏检测能力
泄漏类型 |
检测机制 |
|
---|---|---|
句柄泄漏(FD_LEAK) |
60s一次遍历进程,获取进程fd句柄总数,超过阈值(5000个)时抓取详细句柄信息,同步上报泄漏 |
|
线程泄漏(THREAD_LEAK) |
60s一次遍历进程,获取进程的总线程数,超过阈值(700个)时抓取详细线程名信息,同步上报泄漏 |
|
内存泄漏(MEMORY_LEAK) |
js泄漏(JS_LEAK) |
虚拟机内部进行插桩,当heap使用量超过85% 或者 触发OOM时会抓取heapdump,同步上报该故障 |
native内存泄漏(PSS_MEMORY) |
以应用进程平均动态峰值内存作为基线,60s一次轮询监控,当动态内存峰值超过基线值2倍,判定泄漏,同时触发管控 |
|
ashmem/ion/gpu等内存泄漏 (KERNEL_MEMORY) |
基于ashmem/ion/gpu的基线值,超过基线值时会判定泄漏,同步抓取维测信息 |
以上使用的基线都是默认设置,如果生态在开发过程中需要自行设定基线。
日志获取和日志规格
泄漏日志获取
资源泄漏日志由LeakDetector模块进行管理,可通过以下方式获取:
- 方式一:通过DevEco Testing进行稳定性测试获取日志
DevEco Testing工具会收集设备/data/log/resource_leak/路径下的资源泄漏故障日志,根据进程名、故障和时间分类显示。
泄漏类型
日志文件名称
句柄泄漏(FD_LEAK)
[pid]_fd_leak.txt
线程泄漏(THREAD_LEAK)
[pid]_thread_leak.txt
内存泄漏(MEMORY_LEAK)
js泄漏(JS_LEAK)
memleak-js-[process_name]-[pid]-[tid]-[timestamp].heapsnapshot
native内存泄漏(PSS_MEMORY)
memleak-native-[process_name]-[pid]-sample.txt
memleak-native-[process_name]-[pid]-smaps.txt
memleak-native-[process_name]-[pid]-[timestamp].txt
ashmem/ion/gpu等内存泄漏 (KERNEL_MEMORY)
memleak-kernel-[module]-0-sample.txt
memleak-kernel-[module]-0-[timestamp].txt
- 方式二:通过DevEco Studio主动采集日志
DevEco Studio的profiler模块提供Allocation (获取native调用栈profiler)和 Snapshot(获取JS层heapdump)两种采集方式:
- 方式三:通过hiAppEvent接口订阅
hiAppEvent对外提供了故障订阅接口,可以订阅各类故障打点。资源泄漏故障日志存于/data/storage/el2/log/resourcelimit/路径,日志名统一为RESOURCE_OVERLIMIT_[TIMESTAMP]_[PID].log,可根据日志内容区分文件类型。
句柄泄漏日志规格
故障日志文件名:[pid]_fd_leak.txt(方式一) 或 RESOURCE_OVERLIMIT_[TIMESTAMP]_[PID].log(方式三)
日志头部信息
字段 |
说明 |
---|---|
time |
故障发生时间 |
pid |
发生故障进程的pid,可以用于在流水日志中搜索相关进程信息 |
process |
应用进程包名 |
leaked fd nums |
判定泄漏时获取的句柄数量 (快照) |
time: 2024/06/27 11:55:28
pid: 1380
process: process1
leaked fd nums: 1831
句柄类型详细信息
- Leaked fd Top 10:按照句柄名聚类,获取泄漏句柄中最多的类型,第一列为泄漏数量,第二列为泄漏类型,如下即ashmem类型的句柄存在1337个。
FdCount FileDescriptor ***************************** Leaked fd Top 10: 1337 ashmem 259 socket 119 dmabuf 48 eventfd 42 sync_file 17 eventpoll 3 /sys/kernel/debug/tracing/trace_marker 3 /dev/null 2 /dev/hvgr0
- Dir Type Top 10:针对文件句柄类型,会单独根据文件路径聚类。如下,根据“Leaked fd Top 10”无法看出具体泄漏的类型,但是通过“Dir Type Top 10”能确定是"/data/storage/el2/database/rdb"路径下的文件句柄泄漏,且能大概感知是db泄漏。
Dir Type Top 10: 6175 /data/storage/el2/database/rdb 5 /dev/urandom 3 /sys/kernel/debug/tracing/trace_marker 3 /dev/null 1 anon_inode:[signalfd] 1 /dev/binder 1 /proc/ 1 /system/app/PhoneClone/PhoneClone.hap
特殊类型句柄维测信息
如果Leaked fd Top 10的 TOP句柄信息属于ashmem/socket/pipe/sync_file/dmabuf 这五类特殊类型,且该类型的句柄个数超过1000个,日志中会增加整机详细的维测信息,具体如下:
- ashmem类型句柄
ashmem(即共享内存),如下,由于TOP 1的句柄类型为ashmem,此时抓取了整机ashmem内存的详细信息,里面每一行都是一个单独的ashmem块。
字段
说明
Process_name
持有该ashmem内存块的应用进程包名
Process_ID
发生故障进程的pid,可以用于在流水日志中搜索相关进程信息
Fd
该进程持有的句柄
Applicant_Pid
申请该ashmem内存块的进程pid,可根据识别这个ashmem的来源
Ashmem_name
共享内存的名字,由用户态通过ioctl设置,用来判断存储的资源类型,指向不同的领域
Size
单个ashmem块的大小(单位:B)
***************************** LOGGER_MEMCHECK_ASHMEM_INFO Process ashmem detail info: --------------------------------------------------------------------------------- Process_name Process_ID Fd Cnode_idx Applicant_Pid Ashmem_name Size process1 781 18 328233 781 dev/ashmem/PolicyVolumeMap 384 ........... ...........
- socket类型句柄
socket通信,如下,由于TOP 1的句柄类型为socket,此时抓取了整机socket内存的详细信息。
字段
说明
ProcessName
持有该socket内存块的应用进程包名
ProcessID
发生故障进程的pid,可以用于在流水日志中搜索相关进程信息
Fd
该进程持有的句柄
inode
文件系统对象信息
PeerTid
对端tid(对于有连接的socket,无连接为0)
Process socket info: ---------------------------------------------------- ProcessName ProcessID Fd inode PeerTid process1 6874 3 0 0 ........ .........
- pipe类型句柄
pipe通信,如下,由于TOP 1的句柄类型为pipe,此时以fd维度抓取了整机pipe内存的详细信息。
字段
说明
ProcessName
持有该pipe内存块的应用进程包名
ProcessID
发生故障进程的pid,可以用于在流水日志中搜索相关进程信息
Fd
该进程持有的句柄
PipeName
管道名
inode
文件系统对象信息
MaxUsage
最大使用量
NumAccounted
累计大小量
RingSize
RingBuf大小
Process pipe info: ------------------------------------ ProcessName ProcessID Fd PipeName inode MaxUsage NumAccounted RingSize process1 629 7 / 11 16 16 16 process1 629 8 / 11 16 16 16 ........
- sync_file类型句柄显存,如下,由于TOP 1的句柄类型为sync_file,此时以fd维度抓取了整机sync_file的详细信息。
字段
说明
ProcessName
持有该sync_file内存块的应用进程包名
ProcessID
发生故障进程的pid,可以用于在流水日志中搜索相关进程信息
Fd
该进程持有的句柄
FenceName
sync_file名字
inode
文件系统对象信息
FenceNum
fence个数
TimelineName
fence的Timeline名字
DriverName
驱动名字
Status
fence的状态
Timestamp
fence的时间戳
Process fence info: ---------------------------------------------------- ProcessName ProcessID Fd FenceName inode FenceNum TimelineName DriverName Status Timestamp process1 1309 25 NULL 4186 1 0:online_composer_gfx_primary ukmd_release_fence_2941430 1 91607485502500 process1 1309 26 NULL 4186 1 0:online_composer_gfx_primary ukmd_release_fence_2941430 1 91607485502500 ........
- dmabuf类型句柄dmabuf,如下,由于TOP 1的句柄类型为dmabuf,此时以fd维度抓取了整机dmabuf的详细信息,magic相同表示指向同一块buffer。
字段
说明
Process name
持有该ion内存块的应用进程包名
Process ID
发生故障进程的pid,可以用于在流水日志中搜索相关进程信息
Fd
该进程持有的句柄
size
buffer内存大小(单位:B)
magic
buffer唯一标识
buf->pid
申请者的pid
buf->task_comm
申请buffer的进程名
Process dma_heap info: ---------------------------------------------------- Process name Process ID fd size magic buf->pid buf->task_comm process1 971 23 3145728 36 971 process2 process1 971 24 1048576 38 971 process2 ........
句柄栈信息
当判定句柄泄漏后,会hook 该进程的pipe/open等系统调用10分钟,抓取调用栈,并基于相同调用栈聚类。如下每一行都是一个调用栈,调用顺序为从右到左,其中num后面的数字表示这个调用栈总共有多少个,bt后面为具体调用栈。具体栈信息可通过addr2line解析到对应的函数。
***************************** LOGGER_MEMCHECK_FD_STACK_INFO pid: 12326 get stack time: 2024/06/17 19:16:48 ==============================FdTrack Stack============================== Generated by HiviewDFX @OpenHarmony ==============================Sorted by num============================== num 8272 bt [/system/lib64/libfdleak_tracker.so+0x1fb58] [/system/lib/ld-musl-aarch64.so.1+0x1d3154] [/system/lib/ld-musl-aarch64.so.1+0x148940] [/system/lib64/platformsdk/libuv.so+0x1ab30] [/system/lib64/platformsdk/libuv.so+0x1cbd0] [/system/lib64/module/file/libfs.z.so+0x17109c] [/system/lib64/module/file/libfs.z.so+0x170af4] [/system/lib64/module/file/libfs.z.so+0x1701c8] [/system/lib64/platformsdk/libace_napi.z.so+0x34828] num 3968 bt [/system/lib64/libfdleak_tracker.so+0x1fb58] [/system/lib/ld-musl-aarch64.so.1+0x1d3154] [/system/lib64/platformsdk/libipc_core.z.so+0x4ac64] [/system/lib64/platformsdk/libbackup_kit_inner.z.so+0x532d4] [/system/lib64/platformsdk/libbackup_kit_inner.z.so+0x4f8fc] [/system/lib64/platformsdk/libipc_core.z.so+0x38420] [/system/lib64/platformsdk/libipc_core.z.so+0x4e99c] [/system/lib64/platformsdk/libipc_core.z.so+0x4eb34] [/system/lib64/platformsdk/libipc_core.z.so+0x4edc8] num 3968 bt [/system/lib64/libfdleak_tracker.so+0x1fb58] [/system/lib/ld-musl-aarch64.so.1+0x1d3154] [/system/lib64/platformsdk/libipc_core.z.so+0x4ac64] [/system/lib64/platformsdk/libbackup_kit_inner.z.so+0x532b0] [/system/lib64/platformsdk/libbackup_kit_inner.z.so+0x4f8fc] [/system/lib64/platformsdk/libipc_core.z.so+0x38420] [/system/lib64/platformsdk/libipc_core.z.so+0x4e99c] [/system/lib64/platformsdk/libipc_core.z.so+0x4eb34] [/system/lib64/platformsdk/libipc_core.z.so+0x4edc8]
注意
1、这里统计的是10分钟内全量申请句柄的调用栈,并没有将已经close的去掉。
2、栈信息只有在log版本直接存在,nolog版本若未开“开发者模式”,则不抓取栈信息,如果发现不存在栈信息,可以打开开发者模式抓取。
线程泄漏日志规格
故障日志文件名:[pid]_thread_leak.txt (方式一) 或 RESOURCE_OVERLIMIT_[TIMESTAMP]_[PID].log(方式三)
日志头部信息
字段 |
说明 |
---|---|
time |
检测到线程泄漏的时间 |
pid |
发生故障进程的pid,用于在流水中查询相关进程信息 |
vss |
单个进程全部可访问的地址空间,其大小可能包括还尚未在内存中驻留的部分 |
rss |
单个进程实际占用的内存大小,包括该进程所使用共享库全部内存大小。 |
process |
发生故障的应用包名 |
summary |
判定泄漏时进程线程总数 |
time: 2024/06/27 03:45:19
pid: 41897
vss: 12783644
rss: 2229352
process: process1
summary: 879
线程类泄漏详细信息
- Top 10 Thread Name:按照线程名聚类,获取泄漏最多的线程,第一列为泄漏数量,第二列为线程名称(若创建线程时未指定线程名,则表现出是线程名和进程名相同)。
Top 10 Thread Name: 913 process1 3 gpu-work-client 2 OS_Actor_402 1 IPC_11_13795 1 IPC_12_13796 1 IPC_13_13797
- 线程启动信息:可根据线程启动时间推测。
字段
说明
tid
检测到泄漏时未释放线程的线程号
thread_name
未释放的线程名
start_time(jiffies)
线程创建时间
====================================================== tid thread_name start_time(jiffies) 221 process1 4688297 240 IPC_3_4318 3081382 ... ...
- 线程快照:抓取判定泄漏时线程的调用栈,可由此看下线程做的任务推测线程未退出的原因(如:__pthread_cond_timedwait表示线程正在等待唤醒)。
====================================================== Result: 0 ( no error ) Timestamp:2024-06-27 03:45:20.000 Pid:41897 Uid:1013 Process name:process1 Tid:1527, Name:xxx #00 pc 00000000001b6464 /system/lib/ld-musl-aarch64.so.1(__timedwait_cp+192)(98dc7600a0fc62125e291b93ca336154) #01 pc 00000000001b8468 /system/lib/ld-musl-aarch64.so.1(__pthread_cond_timedwait+188)(98dc7600a0fc62125e291b93ca336154) #02 pc 00000000000c108c /system/lib64/libc++.so(std::__h::condition_variable::wait(std::__h::unique_lock<std::__h::mutex>&)+20)(9cbc937082b3d7412696099dd58f4f78242f9512) #03 pc 000000000024654c /system/lib64/platformsdk/xxx.so(mindspore::Worker::WaitUntilActive()+204)(534ce78b66262dc14658c35fa018662f) #04 pc 000000000023da14 /system/lib64/platformsdk/xxx.so(mindspore::ActorWorker::RunWithSpin()+256)(534ce78b66262dc14658c35fa018662f) #05 pc 000000000023edb0 /system/lib64/platformsdk/xxx.so(void* std::__h::__thread_proxy[abi:v15004]<std::__h::tuple<std::__h::unique_ptr<std::__h::__thread_struct, std::__h::default_delete<std::__h::__thread_struct>>, void (mindspore::ActorWorker::*)(), mindspore::ActorWorker*>>(void*)+60)(534ce78b66262dc14658c35fa018662f) #06 pc 00000000001baac0 /system/lib/ld-musl-aarch64.so.1(start+236)(98dc7600a0fc62125e291b93ca336154) ........
内存泄漏日志规格
JS内存泄漏
故障日志文件名:memleak-js-[process_name]-[pid]-[tid]-[timestamp].heapsnapshot(方式一) 或 RESOURCE_OVERLIMIT_[TIMESTAMP]_[PID].log(方式三)
该文件记录了对象的详细信息,可通过IDE打开展示。
native内存泄漏
故障日志文件名:泄漏日志获取中方式一和方式三文件名不同,方式三为RESOURCE_OVERLIMIT_[TIMESTAMP]_[PID].log,根据内容区分,方式一如下所示:
- 日志文件:memleak-native-[process_name]-[pid]-sample.txt,里面展示了进程号,进程名,基线值,内存采样的情况,可以直观的观察到内存的变化情况。
字段
说明
threshold
系统设定的该进程基线(也可由应用自身通过setAppResourceLimit接口设置)
PssMemory
记录了realtime时刻采集的PSS值,用于和threshold比较
pid: 2017 processName: process1 threshold: 32808(KB) TopPssMemory: 118643(KB) RealPssMemory: 84014(KB) realtime: 2024/06/26 04:42:30 index time(s) PssMemory(KB) realtime 0 118264 81890 2024/06/26 04:00:39 1 118385 81970 2024/06/26 04:02:39 ......
- 日志文件:memleak-native-[process_name]-[pid]-smaps.txt
字段
说明
threshold
系统设定的该进程基线(也可由应用自身通过setAppResourceLimit接口设置)
PssMemory
记录了realtime时刻采集的PSS值,用于和threshold比较
LOGGER_MEMCHECK_MEMINFO
下方记录了整机meminfo内存信息,如MemTotal、MemFree等
LOGGER_MEMCHECK_SMAPS_INFO
下方记录了该进程的smaps汇总信息
LOGGER_MEMCHECK_DETIAL_INFO
下方记录了该进程的jemalloc快照详细信息
Generated by HiviewDFX @OpenHarmony LOGGER_MEMCHECK_GERNAL_INFO pidNumber: 2017 processName: process1 PidStartTime: 1602 TopPssMemory: 83505 ***************************** LOGGER_MEMCHECK_MEMINFO MemTotal: 11332540 kB MemFree: 1686056 kB ...... LOGGER_MEMCHECK_SMAPS_INFO -------------------------------[memory]------------------------------- Shared Shared Private Private Size Rss Pss Clean Dirty Clean Dirty Swap SwapPss Counts Name 2048 0 0 0 0 0 0 0 0 1 /dev/__parameters__/param_sec_dac ....... ***************************** LOGGER_MEMCHECK_DETIAL_INFO allocated nmalloc (#/sec) ndalloc (#/sec) nrequests (#/sec) nfill (#/sec) nflush (#/sec) small: 183785560 12591759 619 10371251 510 1289491 63 1313204 64 956094 47 large: 31059968 3359 0 2946 0 3359 0 3359 0 0 0 total: 214845528 12595118 619 10374197 510 1292850 63 1316563 64 956094 47 ...... bins: size ind allocated nmalloc (#/sec) ndalloc (#/sec) nrequests (#/sec) nshards curregs curslabs nonfull_slabs regs pgs util nfills (#/sec) nflushes (#/sec) nslabs nreslabs (#/sec) n_lock_ops (#/sec) n_waiting (#/sec) n_spin_acq (#/sec) n_owner_switch (#/sec) total_wait_ns (#/sec) max_wait_ns max_n_thds 8 0 198920 163820 8 138955 6 119703 5 1 24865 56 19 512 1 0.867 6526 0 4008 0 96 26995 1 10990 0 0 0 0 0 1226 0 0 0 0 0 16 1 1802688 1143707 56 1031039 50 221165 10 1 112668 563 309 256 1 0.781 105471 5 82548 4 1942 80503 3 191126 9 0 0 14 0 4316 0 0 0 0 0 32 2 9954560 1867465 91 1556385 76 267993 13 1 311080 2614 503 128 1 0.929 177825 8 136745 6 7713 176923 8 325128 15 2 0 52 0 8139 0 0 0 0 1 48 3 35382816 3763756 185 3026614 148 300952 14 1 737142 2953 220 256 3 0.975 371881 18 283650 13 12022 60637 2 667997 32 2 0 17 0 2725 0 0 0 0 1 ......
- 栈信息日志文件:memleak-native-[process_name]-[pid]-[timestamp].txt
- 检测到泄漏后抓取的15min 进程内存trace,可将日志如下图通过Open File加载到IDE profiler解析。
- All Heap:框选后展示抓取内存的15分钟内的内存情况,记录了hook malloc等系统调用的堆栈。Native日志是以so+偏移的形式展示调用栈(每一行表示一次内存分配行为调用栈),需要结合符号表进一步分析。
- Existing:剩余未释放的内存
- #Existing:剩余未释放的内存的申请次数
- Total Bytes:申请的总内存
- #Total:申请的总内存的申请次数
- Transient:已释放的内存
- #Transient:已释放的内存的申请次数
点击Call Info可以看到如下的火焰图,将类型选择为Created & Existing,图中栈调用关系为从下到上,长度越长代表在剩余内存中所占的比例越高。
说明
部分栈单看Existing可能感觉泄漏不大或者和检测到的内存峰值相差很多,但是栈里只是抓取的15分钟内的堆栈信息和内存申请,很多进程泄漏是以几十甚至几百小时为单位的,长时间的泄漏达到上报时的泄漏大小。
- All Anonymous VM:框选后记录了当前hook mmap系统调用的堆栈信息。
同样选择Created & Existing,表示在hook抓取内存申请未释放的。长度越长代表在剩余内存中占用越多,优先排查。
- 检测到泄漏后抓取的15min 进程内存trace,可将日志如下图通过Open File加载到IDE profiler解析。
ashmem/gpu/ion内存泄漏
- 日志文件:memleak-kernel-[module]-0-sample.txt(方式一) 或 RESOURCE_OVERLIMIT_[TIMESTAMP]_[PID].log(方式三)
字段
说明
memoryName
内核内存类型(ashmem/gpu/ion)
softThreshold
设定的软门限(超过8个采样周期,即30+分钟超过软门限后判定泄漏)
hardThreshold
设定的硬门限(单次超过硬门限后判定泄漏)
topMemory
检测到的内核内存峰值
time(s)
采样kernel内存的时间
kernelMemory(KB)
抓取的内核内存峰值
realtime
抓取该内存峰值的时间点
memoryName:gpu softThreshold:2300(MB) hardThreshold:3450(MB) topMemory:4876824(KB) time(s) kernelMemory(KB)realtime 247681 4876824 2024/06/24 08:27:52
- 日志文件:memleak-kernel-[module]-0-[timestamp].txt(方式一)
检测到ashmem/gpu/ion内存泄漏时,会抓取整机ashmem/gpu/ion内存信息,ashmem/ion与句柄泄漏ashmem/dmabuf日志规格相同,参考ashmem/dmabuf类型句柄,gpu内存规格信息如下:
一个ctx为一个进程,根据used summary识别哪个进程占用最大进行分析,然后进一步分析channel(按照单个对象大小排序)确定是否存在泄漏,如下4 / 160 表示有4个对象,总大小160B,即单个对象大小40B。
LOGGER_MEMCHECK_PROC_INFO render_service ctx_1 1689 1455 used summary:3362426880 grow:0 driver:10432512 kmd:3260416 jit:131072 Channel: xx default device (Total memory: 730594) 1: 2 / 2 6: 4 / 160 7: 6 / 384 8: 163 / 20928 9: 1573 / 604160 10: 48 / 24576 11: 2 / 2048 13: 2 / 12800 15: 4 / 65536 ...... ctx_3 1689 1455 used summary:3522560 grow:0 driver:3321856 kmd:3260416 jit:0 ctx_2 1689 1455 used summary:3362426880 grow:0 driver:10432512 kmd:3260416 jit:131072 process1 ...... ...... ctx_0 1689 1455 used summary:28672 grow:0 driver:28672 kmd:0 jit:0 process2 ......
问题定位步骤与思路
句柄泄漏问题分析方法
案例一:
某应用上报句柄泄漏,/data/storage/el1/bundle/entry.hap句柄个数 5000+,推测entry.hap泄漏。
Leaked fd Top 10:
5663 /data/storage/el1/bundle/entry.hap
125 ashmem
22 eventpoll
22 eventfd
16 pipe
13 socket
Top Dir Type 10:
5711 /data/storage/el
4 /dev/urandom
3 /dev/null
2 /proc/
2 /dev/binder
分析:
1.包管理接口问题,open hap未释放句柄,但这个问题只报在应用进程内,且只有XXX应用单应用上,其他未复现,暂时排除;
2.应用自身问题,不断open 这个文件句柄未释放,传递给三方能力开发者(确认为该问题)。
案例二:
某service上报句柄泄漏,/system/lib占用的so句柄个数超过5000+
time: 2024/07/09 08:32:16
pid: 1386
process: XXX
leaked fd nums: 5022
FdCount FileDescriptor
*****************************
Leaked fd Top 10:
179 /system/lib64/libinsightintent_common.z.so
179 /system/lib64/platformsdk/libzuri.z.so
179 /system/lib64/platformsdk/libpdfinner.z.so
179 /system/lib64/platformsdk/libwant.z.so
179 /system/lib64/platformsdk/libtokenid_sdk.z.so
179 /system/lib64/chipset-pub-sdk/libcrypto_openssl.z.so
179 /system/lib64/chipset-pub-sdk/libjsoncpp.z.so
179 /system/lib64/libai_datasync_innerapi.z.so
179 /system/lib64/libai_framework_innerapi.z.so
179 /system/lib64/libai_label_detect_innerapi.z.so
Top Dir Type 10:
5012 /system/lib
3 /dev/null
1 /dev/binder
1 /dev/kmsg
1 /dev/tty
1 /sys/kernel/debug/tracing/trace_marker
*****************************
LOGGER_MEMCHECK_FD_STACK_INFO
pid: 1386
get stack time: 2024/07/09 08:42:22
==============================FdTrack Stack==============================
Generated by HiviewDFX @OpenHarmony
==============================Sorted by num==============================
num 1134 bt [/system/lib64/libfdleak_tracker.so+0x20248] [/system/lib/ld-musl-aarch64.so.1+0x144dfc] [/system/lib/ld-musl-aarch64.so.1+0xba5b0] [/system/lib/ld-musl-aarch64.so.1+0xd1ac] [/system/lib/ld-musl-aarch64.so.1+0x7c30] [/system/lib/ld-musl-aarch64.so.1+0x48e8] [/system/lib/ld-musl-aarch64.so.1+0x62bc] [/system/lib64/platformsdk/libsamgr_common.z.so+0xdb88] [/system/lib64/platformsdk/libsamgr_common.z.so+0xde58]
num 42 bt [/system/lib64/libfdleak_tracker.so+0x20248] [/system/lib/ld-musl-aarch64.so.1+0x144dfc] [/system/lib/ld-musl-aarch64.so.1+0xba5b0] [/system/lib/ld-musl-aarch64.so.1+0xd1ac] [/system/lib/ld-musl-aarch64.so.1+0x7c30] [/system/lib/ld-musl-aarch64.so.1+0x6250] [/system/lib64/platformsdk/libsamgr_common.z.so+0xdb88] [/system/lib64/platformsdk/libsamgr_common.z.so+0xde58] [/system/lib64/platformsdk/libsystem_ability_fwk.z.so+0x127d0]
num 1 bt [/system/lib64/libfdleak_tracker.so+0x20248] [/system/lib/ld-musl-aarch64.so.1+0x144dfc] [/system/lib64/platformsdk/libfwmark_client.z.so+0x4208] [/system/lib64/chipset-pub-sdk/libhisysevent.z.so+0x13abc] [/system/lib64/chipset-pub-sdk/libhisysevent.z.so+0x13eec] [/system/lib64/chipset-pub-sdk/libhisysevent.z.so+0x7b08] [/system/lib64/platformsdk/libsamgr_common.z.so+0x26c38] [/system/lib64/platformsdk/libsamgr_common.z.so+0x26998] [/system/lib64/platformsdk/libsamgr_common.z.so+0x294ec]
END
- /system/lib句柄超过5000个,从Leaked fd Top 10信息可以看出都是so,推测为dlopen获取的句柄未释放。
- 根据“LOGGER_MEMCHECK_FD_STACK_INFO”下hook open等系统调用获取的调用栈,发现第一个栈在hook的10分钟内申请了1134次句柄,高度怀疑这个栈。
- 获取这些so的符号表(libfdleak_tracker.so是维测用的so,可忽略),通过addr2line获取调用栈。
- 对应的代码调用顺序如下:
dlopen获取的句柄的位置如下,fd存在saProfile,需要进一步查看saProfile的释放时机。
搜索saProfile的释放位置,发现只有在ParseUtil对象析构时才会释放fd资源。
找到调用者的位置,发现定义了一个类内的私有变量,而这个类的对象一直没析构,导致profileParser_一直没析构,从而导致fd资源一直未释放。
线程泄漏分析方法
某应用总线程数超700,上报线程泄漏。
- 看线程数量发现 thread1 线程数达到677:
- 看这些线程的状态(如:死锁、空闲等等)
查看线程快照发现栈顶都是__pthread_cond_timedwait,线程都处于等待唤醒,但是线程数较多,优先排查线程创建的地方,往下发现全部归属于libijkplayer.so 内部(开发者so)的线程池,怀疑线程池过大造成。
内存泄漏分析方法
JS泄漏
应用侧声明的class、struct、enum,以及通过new创建的实例对象、build中声明的组件、lambda表达式创建的匿名函数,对应在ArkTs虚拟机中都会创建对应的内存对象,虚拟机会自动在内存对象的属性中引用依赖上下文的闭包环境,从而产生隐式的一些引用关系,比较容易产生内存泄漏。
- 如果自动上报的只有1份heapdump,需要根据实际应用逻辑,按照Retained Size排序,看是否当前对象个数超过业务逻辑预期(比如X1000、X10000个对象的,明显是存在异常的),如果超过就根据该对象的引用关系查找泄漏点;如下案例,存在26个Note JSObject对象,与实际业务不符合,需要进一步查其引用关系判断是否泄漏:
- 使用IDE Snapshot能力可抓取两次heapdump,通过比较两次heapdump的对象delta进行分析:
Statistics功能可以用来查看全量内存信息,Comparison功能可以用来对比两份内存快照。
如下:
- 在profiler模块,在操作前后采集两次snapshot看一下delta(按照delta排序);
- 找到和业务相关的对象;
- 展开业务代码根节点,通过Distance观察引用路径,值越小表示离GC Root的距离越近,展开节点依次找到最小的值,看一下这些业务对象个数是否符合当前这个页面的预期,是否存在循环引用或者闭包问题导致泄漏。
注意
每次抓snapshot会触发1次GC,snapshot文件中展示的对象都是经过GC后因GC根可达无法释放的对象。
Native泄漏
- 分析采样日志
某应用 PSS泄漏,峰值内存TopPssMemory为1.3GB左右,且内存一直增长。
- 分析smaps日志
- native泄漏有多种泄漏类型,具体可根据表格定位分析是哪一块泄漏,其中总PSS内存即sample文件的采样内存(可用最后一个)。
泄漏类型
判定方法
定位方法
虚拟机对象泄漏
搜索关键字“ArkTS”,PSS和SWAP PSS列的值加起来,如果超过总PSS内存的50%,则说明是虚拟机对象内存过大
同JS泄漏 heapdump分析方法
堆内存泄漏
搜索关键字“jemalloc”,PSS和SWAP PSS列的值加起来,如果超过总PSS内存的50%,则说明是堆内存过大
基于nmd和profiler分析
堆内存泄漏有两种可能:
1、调用ARKUI的接口或者开发者的so,直接malloc申请的堆内存过大
2、虚拟机对象持有堆内存引用,对象泄漏导致native内存过大
ashmem泄漏
搜索关键字“/dev/ashmem”,如果超过总PSS内存的50%,则说明是ashmem内存过大
同ashmem泄漏分析方法
1、开发者使用image组件、pixmap组件可能未释放
2、开发者直接通过系统调用申请
anon类型较大
单个anon类型占用内存较大
怀疑mmap内存未释放,直接排查profiler栈,框选 All Anonymous VM,筛选Created & Existing,排查内存占用最多的部分
本例当前应用 jemalloc大小 1GB左右,怀疑堆内存泄漏。
- 基于nmd(堆内存快照,判定泄漏时,堆内存布局的快照)和profiler分析:
nmd看堆内存总共1.3GB左右:
native malloc detail记录抓取进程native日志时内存的快照信息,主要关注其size和allocated两列,有没有哪一块特别大,如果有,假设size为a(如64),就去调用栈中搜索size为a的内存申请,重点分析这行调用栈,极可能是泄漏点。
size向下取整,要看一下所有size的块里面哪个比较大。
说明
Size:用户申请的内存经过对齐后的大小,jemalloc对齐size的分割是按照一个特定算法算的,8字节是最小单位,从第二个size开始,最小step是16,一个size到它的两倍size之间有4个分档。用户态传入的申请大小会向下对齐到离它最近的size中。
Allocated:这个size申请的总内存
如下图:
单次申请80字节的堆内存有135MB左右
单次申请128字节的堆内存有312MB左右
优先怀疑这两个内存块。框选All Heap,解析profiler,选择Created & Existing。
搜索80字节的,排查该堆栈:
搜索128字节的,排查该堆栈:
将堆栈反馈给三方能力开发者。
- native泄漏有多种泄漏类型,具体可根据表格定位分析是哪一块泄漏,其中总PSS内存即sample文件的采样内存(可用最后一个)。
ashmem泄漏
图片共享内存一般有以下几种命名方式:
/dev/ashmem/PixelMap RawData, uniqueId: xxxx_xx
/dev/ashmem/JPEG RawData, uniqueId: 1845_2 (deleted)
/dev/ashmem/EXT RawData
这些内存都是由图片编解码框架提供的编解码工具申请的,申请代码如下:
// 以EXT RawData内存为例
const static string EXT_SHAREMEM_NAME = "EXT RawData";
static uint32_t ShareMemAlloc(DecodeContext &context, uint64_t count)
{
...
auto fd = make_unique<int32_t>();
*fd = AshmemCreate(EXT_SHAREMEM_NAME.c_str(), count);
...
}
解码框架本身没有问题,一旦完成解码,ashmem的所有权会转移给C++的PixelMap对象,如果是ashmem泄漏,基本上可以断定是C++层的PixelMap泄漏。
开发者首先排查是否存在如下可能:
- 开发者的应用使用了 Node-API 在native层创建并使用decode()解码获取PixelMap,但是可能存在申请未释放、把PixelMap缓存到应用生命周期的容器类中、引用计数多加等可能;
- 如果应用根本就没有使用 Node-API 实现C++代码,那么排查是否使用JS层的PixelMap,可能存在JS对象泄漏或者缓存太多导致PixelMap大量占用内存,可使用IDE 抓两次snapshot看一下对象的增量分析;
- 如果上述两种情况都不存在,那么很有可能ArkUI组件内部实现存在PixelMap泄漏。
图片文件句柄泄漏问题分析
- 问题现象
smaps中存在数量较多的图片文件路径为名称的内存块。
- 分析业务
从jsheap中可以看到,js侧ForEach数据源未发生泄漏,可以暂时排除应用业务逻辑的问题。
分析代码后发现以下两处怀疑点:
1.sceneSessionManager.getSessionSnapshot接口内部native实现是否可能存在泄漏;
2.getImageInfo中实现引用了文件句柄,导致泄漏。
通过注释代码后,可以定位到泄漏点在getImageInfo内部image.createImageSource,属于工具范围存在句柄泄漏。
- 推荐建议(问题总结)
应用侧对于不用的PixelMap要及时释放引用,每个未释放的PixelMap都会在底层产生一条/dev/ashmem/XXXX RawData 的共享内存占用。
ion泄漏
- 日志中搜索"Total dmaheap size of"查看哪个进程占用ION内存最多,如图:process6内存最多,定界到进程,找对应领域分析。
- 搜索magic这一列,magic相同表示属于用一块buffer,正常如下,应该是存在buffer流转,buffer被多个进程共享。
Process name Process ID fd size magic buf->pid buf->task_comm process1 965 41 1048576 6507 965 process1 process5 965 43 1048576 6507 965 process1 process6 965 44 1048576 6507 965 process1
- 如果存在如下的大量只存在一个进程内部的buffer,高概率怀疑存在泄漏,大概率是一个进程已经释放了,但是另一个进程未释放。
Process name Process ID fd size magic buf->pid buf->task_comm process6 39654 604 45441024 5105 1331 process1
- 目前因为采用了统一渲染机制,大部分ION内存都是在Render_service进程分配使用的,如果发现应用使用ION超标了,那么按照历史经验,高概率怀疑是PixelMap C++对象泄漏。
- 开发者首先排查是否存在如下可能:
- 开发者的应用使用了 Node-API 在native层创建并使用decode()解码获取PixelMap,但是可能存在申请未释放、把PixelMap缓存到应用生命周期的容器类中、引用计数多加等可能;
- 如果应用根本就没有使用 Node-API 实现C++代码,那么排查是否使用JS层的PixelMap,可能存在JS对象泄漏或者缓存太多导致PixelMap大量占用,可使用IDE 抓两次snapshot看一下对象的增量分析。
如果上述两种情况都不存在,那么很有可能ArkUI组件内部实现存在PixelMap泄漏。
更多推荐
所有评论(0)