鸿蒙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抓取内存申请未释放的。长度越长代表在剩余内存中占用越多,优先排查。

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
  1. /system/lib句柄超过5000个,从Leaked fd Top 10信息可以看出都是so,推测为dlopen获取的句柄未释放。
  2. 根据“LOGGER_MEMCHECK_FD_STACK_INFO”下hook open等系统调用获取的调用栈,发现第一个栈在hook的10分钟内申请了1134次句柄,高度怀疑这个栈。
  3. 获取这些so的符号表(libfdleak_tracker.so是维测用的so,可忽略),通过addr2line获取调用栈。
  4. 对应的代码调用顺序如下:

    dlopen获取的句柄的位置如下,fd存在saProfile,需要进一步查看saProfile的释放时机。

    搜索saProfile的释放位置,发现只有在ParseUtil对象析构时才会释放fd资源。

    找到调用者的位置,发现定义了一个类内的私有变量,而这个类的对象一直没析构,导致profileParser_一直没析构,从而导致fd资源一直未释放。

线程泄漏分析方法

某应用总线程数超700,上报线程泄漏。

  1. 看线程数量发现 thread1 线程数达到677:

  2. 看这些线程的状态(如:死锁、空闲等等)

    查看线程快照发现栈顶都是__pthread_cond_timedwait,线程都处于等待唤醒,但是线程数较多,优先排查线程创建的地方,往下发现全部归属于libijkplayer.so 内部(开发者so)的线程池,怀疑线程池过大造成。

内存泄漏分析方法

JS泄漏

应用侧声明的class、struct、enum,以及通过new创建的实例对象、build中声明的组件、lambda表达式创建的匿名函数,对应在ArkTs虚拟机中都会创建对应的内存对象,虚拟机会自动在内存对象的属性中引用依赖上下文的闭包环境,从而产生隐式的一些引用关系,比较容易产生内存泄漏。

  1. 如果自动上报的只有1份heapdump,需要根据实际应用逻辑,按照Retained Size排序,看是否当前对象个数超过业务逻辑预期(比如X1000、X10000个对象的,明显是存在异常的),如果超过就根据该对象的引用关系查找泄漏点;如下案例,存在26个Note JSObject对象,与实际业务不符合,需要进一步查其引用关系判断是否泄漏:

  2. 使用IDE Snapshot能力可抓取两次heapdump,通过比较两次heapdump的对象delta进行分析:

    Statistics功能可以用来查看全量内存信息,Comparison功能可以用来对比两份内存快照。

如下:

  1. 在profiler模块,在操作前后采集两次snapshot看一下delta(按照delta排序);
  2. 找到和业务相关的对象;
  3. 展开业务代码根节点,通过Distance观察引用路径,值越小表示离GC Root的距离越近,展开节点依次找到最小的值,看一下这些业务对象个数是否符合当前这个页面的预期,是否存在循环引用或者闭包问题导致泄漏。

    注意

    每次抓snapshot会触发1次GC,snapshot文件中展示的对象都是经过GC后因GC根可达无法释放的对象。

Native泄漏

  1. 分析采样日志

    某应用 PSS泄漏,峰值内存TopPssMemory为1.3GB左右,且内存一直增长。

  2. 分析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字节的,排查该堆栈:

      将堆栈反馈给三方能力开发者。

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泄漏。

开发者首先排查是否存在如下可能:

  1. 开发者的应用使用了 Node-API 在native层创建并使用decode()解码获取PixelMap,但是可能存在申请未释放、把PixelMap缓存到应用生命周期的容器类中、引用计数多加等可能;
  2. 如果应用根本就没有使用 Node-API 实现C++代码,那么排查是否使用JS层的PixelMap,可能存在JS对象泄漏或者缓存太多导致PixelMap大量占用内存,可使用IDE 抓两次snapshot看一下对象的增量分析;
  3. 如果上述两种情况都不存在,那么很有可能ArkUI组件内部实现存在PixelMap泄漏。

图片文件句柄泄漏问题分析

  • 问题现象

    smaps中存在数量较多的图片文件路径为名称的内存块。

  • 分析业务

    从jsheap中可以看到,js侧ForEach数据源未发生泄漏,可以暂时排除应用业务逻辑的问题。

    分析代码后发现以下两处怀疑点:

    1.sceneSessionManager.getSessionSnapshot接口内部native实现是否可能存在泄漏;

    2.getImageInfo中实现引用了文件句柄,导致泄漏。

    通过注释代码后,可以定位到泄漏点在getImageInfo内部image.createImageSource,属于工具范围存在句柄泄漏。

  • 推荐建议(问题总结)

    应用侧对于不用的PixelMap要及时释放引用,每个未释放的PixelMap都会在底层产生一条/dev/ashmem/XXXX RawData 的共享内存占用。

ion泄漏

  1. 日志中搜索"Total dmaheap size of"查看哪个进程占用ION内存最多,如图:process6内存最多,定界到进程,找对应领域分析。

  2. 搜索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
  3. 如果存在如下的大量只存在一个进程内部的buffer,高概率怀疑存在泄漏,大概率是一个进程已经释放了,但是另一个进程未释放。
    Process name       Process ID            fd             size            magic         buf->pid   buf->task_comm
    process6             39654               604          45441024             5105              1331       process1
  4. 目前因为采用了统一渲染机制,大部分ION内存都是在Render_service进程分配使用的,如果发现应用使用ION超标了,那么按照历史经验,高概率怀疑是PixelMap C++对象泄漏。
  5. 开发者首先排查是否存在如下可能:
    • 开发者的应用使用了 Node-API 在native层创建并使用decode()解码获取PixelMap,但是可能存在申请未释放、把PixelMap缓存到应用生命周期的容器类中、引用计数多加等可能;
    • 如果应用根本就没有使用 Node-API 实现C++代码,那么排查是否使用JS层的PixelMap,可能存在JS对象泄漏或者缓存太多导致PixelMap大量占用,可使用IDE 抓两次snapshot看一下对象的增量分析。

      如果上述两种情况都不存在,那么很有可能ArkUI组件内部实现存在PixelMap泄漏。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值