• 电话本内存泄露


    一、问题背景

    测试使用monkey工具,压力测试主题商店应用时,最后出现电话本内存泄露信息:

    appCrashed_android.process.contacts_2016-12-30 00_34_36.txt
    // CRASH: android.process.contacts (pid 5172)


    // Short Msg: java.lang.OutOfMemoryError

    刚开始看到这个问题,感觉一头雾水,为何测试主题商店应用,会导致电话本内存泄露呢? 这两个看起来八竿子打不着的应用, 会存在着怎样的纠葛,当时不知。

    项目要继续,问题要解决。之前没有处理过OOM问题,只好查阅资料,询问同事协助处理。

    二、问题分析

    首先想确认问题的复现概率, 测试说基本上跑个两三个小时的monkey测试,就能复现,于是我自己也本地在尝试,果然能否复现。向导主题商店的测试内容主要为切换主题,于是就编写脚本,

    压力测试切换主题,看能否使问题复现的时间缩短。

    切换主题的脚本:

    @echo off

    adb remount
    adb push 240699_Men.theme /sdcard/Themes/

    pause

    echo --------start to change themes-----

     ##循环切换主题, 1000次

    FOR /l %%i IN (1,1,1000) DO (
    adb shell am startservice -n com.nearme.themespace/com.nearme.themespace.services.ThemeApplyService --es THEME_PATH /sdcard/Themes/240699_Men.theme

    ##每次间隔8秒钟
    ping -n 8 127.0.0.1>nul
    echo %%i
    )
    pause

    通过脚本运行,果然复现时间缩短了很多,很快就能复现。

    接下来就是查找哪里出现的问题了,在网上找到一篇老外分析短信内存泄露的方法,分析中有如何使用脚本工具获取内存信息,很好用:

    http://stevevallay.github.io/blog/2014/11/17/memory-leak/

    文中说明为了获取内存信息,我们需要用到linux提供的 

    procrank
    procmem

    这两个工具,一般我们的手机中不会自带这两个工具,需要去编译的版本中查找拷贝过来使用。

    具体项目工具适配方法:
    1、由于procmem和 Procrank 工具在我们的手机中一般没有放置,因此在调试各个项目时需要到各个项目编译的out下面的去找到对应的工具以及so库拷贝到目录中进行替换使用,
    目录根据机器是32位还是 64位 有所不同:
    out argetproductmsm8952_64systemlib
    out argetproductmsm8952_64systemlib64


    2、bat脚本也需要根据32位和64位push so库到对应目录, shell脚本需要根据需要调试的应用进程进行修改:

    放置工具到手机中的bat脚本如下:

    adb remount

    adb shell rm -rf /sdcard/mem_info_log/

    adb push libpagemap.so /system/lib64

    adb push procrank /system/xbin
    adb push procmem /system/xbin

    adb shell chmod 777 /system/xbin/procrank
    adb shell chmod 777 /system/xbin/procmem

    pause

    ###放置到手机后台运行导出内存信息的shell脚本

    adb push log.sh /system/bin

    adb shell chmod 777 /system/bin/log.sh

    adb shell sh /system/bin/log.sh &

    pause

    在手机中运行的shell脚本文件参照老外的写法如下:

    #!/system/bin/sh
    set `ps| grep android.process.contacts`
    pidOfMms=$2
    echo "pid of contacts is: $pidOfMms"

    set `ps|grep com.nearme.themespace`
    pidOfPhone=$2
    echo "pid of themespace is: $pidOfPhone"

    if [ ! -d "/sdcard/mem_info_log" ]; then
    mkdir "/sdcard/mem_info_log"
    echo "create new dir /sdcard/mem_info_log"
    fi

    while [ -d "/sdcard/mem_info_log" ]
    do
    echo "start capture mem_info_log"
    t=`date +"%Y-%m-%d-%H-%M-%S"`
    echo $$ > /sdcard/mem_info_log/pid.log
    procrank > "/sdcard/mem_info_log/procrank"$t".log"
    procmem -p $pidOfMms > "/sdcard/mem_info_log/procmem.contacts"$t".log"
    procmem -p $pidOfPhone > "/sdcard/mem_info_log/procmem.themespace"$t".log"
    dumpsys meminfo android.process.contacts > "/sdcard/mem_info_log/meminfo.contacts"$t".log"
    dumpsys meminfo com.nearme.themespace > "/sdcard/mem_info_log/meminfo.themespace"$t".log"
    cat /proc/$pidOfPhone/maps > "/sdcard/mem_info_log/maps.themespace"$t".log"
    cat /proc/$pidOfMms/maps > "/sdcard/mem_info_log/maps.contacts"$t".log"
    sleep 300
    done

    脚本中每隔五分钟会使用 

    procrank
    procmem

    工具分别导出手机在压力测试过程中当前的内存信息,形成一个个文本放置在手机的一个目录下面,便于后续分析。

    其中最终看到的有用信息在memInfo中:

    Pss Private Private Swapped Heap Heap Heap
    Total Dirty Clean Dirty Size Alloc Free
    ------ ------ ------ ------ ------ ------ ------
    Native Heap 4160 3992 0 0 7936 6563 1372
    Dalvik Heap 13589 13464 0 0 23033 22945 88
    Dalvik Other 635 632 0 0
    Stack 212 212 0 0
    Ashmem 6 4 0 0
    Other dev 4 0 4 0
    .so mmap 797 80 28 0
    .apk mmap 479 0 116 0
    .ttf mmap 11 0 0 0
    .dex mmap 3644 8 3632 0
    .oat mmap 2844 0 796 0
    .art mmap 1716 940 0 0
    Other mmap 96 8 16 0
    Unknown 110 104 0 0
    TOTAL 28303 19444 4592 0 30969 29508 1460

    App Summary
    Pss(KB)
    ------
    Java Heap: 14404
    Native Heap: 3992
    Code: 4660
    Stack: 212
    Graphics: 0
    Private Other: 768
    System: 4267

    TOTAL: 28303 TOTAL SWAP (KB): 0

    Objects
    Views: 389 ViewRootImpl: 1
    AppContexts: 7 Activities: 4
    Assets: 3 AssetManagers: 2
    Local Binders: 62 Proxy Binders: 27
    Parcel memory: 9 Parcel count: 36
    Death Recipients: 0 OpenSSL Sockets: 0

    SQL
    MEMORY_USED: 0
    PAGECACHE_OVERFLOW: 0 MALLOC_SIZE: 0

    看到了Activities的应用在持续增长,这样就能够确认应用确实存在OOM了,于是开始查对应Activity里面的代码。

    最后发现在一个activity中存在了注册观察者处理,但是没有注销,其注册方式为:

    getActivity().getContentResolver().registerContentObserver

    问题的巧合之处就是原本正常的activity的生命周期其实每次就一次onCreate, 注册的调用是在这个方法中,如果是反复直接进入这个activity, 最多也就有一次引用,

    因为再次进入是直接运行onstart的生命周期,不会在走入onCreate中了,所以如果反复测试电话本应用本身,此问题复现不了,反而是由于切换主题后,导致电话本又重新运行了

    onCreate方法,这样每次都会运行注册,增加引用次数,导致后面出现引用过多,activity迟迟不能回收后导致OOM, 使用脚本观察,发现一般在15次左右就会出现OOM, 此时

    电话本应用的内存已经接近120M, 这应该也是系统分配给一个应用的最大内存使用数值。

    三、问题解决

    当然发现问题原因后,解决起来就比较容易了,在onDestroy中增加注销即可:

    getActivity().getContentResolver().unregisterContentObserver

    随后再网上查阅了一下常用的OOM问题:

    https://juejin.im/entry/5762b1d7816dfa00544680a0

     我这个问题就是属于

    监听器没有注销造成的内存泄漏

    所以注册和注销一定要记得成对使用。

    问题过后又细想了一下为何这种注册没有注销会导致OOM, 于是猜测这种监听器的底层实现应该是使用了静态static 列表,将每个注册传递过来的activity或者其他参数

    放入静态列表中,所以会导致无法回收。 并不是所有的用getActivity()作为参数传递过去之后都会导致OOM,比如初始化中获取资源文件,有时候也会使用这个方法传递参数

    作为context, 这种就是没有问题的:

    FeatureOption.init(getActivity());

    不要风声鹤唳,以后所有的地方都不敢获取activity 传递参数了。

  • 相关阅读:
    CSP 命令行选项(201403-3)
    ElasticSearch7.10的查询数据-简单查询
    ElasticSearch 种映射参数详解-理论学习02
    Elasticsearch7.10 -理论学习01
    ElasticSearch7.10索引
    ElasticSearch7.10的分词器
    ElasticSearch-7.10安装-2
    ElasticSearch第一天
    Idea的注释配置
    深圳第一站被骗消费3960元
  • 原文地址:https://www.cnblogs.com/yangwubo/p/6609956.html
Copyright © 2020-2023  润新知