Android Memory(四) -- 问题定位&解决方案1

·  阅读 709

Android-Go.jpeg

前言

     在工作这几年,我一直深受内存问题的困扰,在和内存的不断抗争中,我逐渐积累了一些内存的知识,接来下来我会用几篇文章简单记录一下这几年的我学到的内存相关的经验。另外,本系列文章不去过多的分析Linux底层代码,只是探讨遇到内存问题时的解决方法论。 以下是全部文章的标题和链接:

  1. Android Memory(一) -- 内存基础知识
  2. Android Memory(二) -- 应用内存占用分析
  3. Android Memory(三) -- 问题原因分析
  4. Android Memory(四) -- 问题定位&解决方案1
  5. Android Memory(五) -- 问题定位&解决方案2
  6. Android Memory(六) -- 内存日常监控

    前几篇文章,我介绍了各种内存问题的异常情况,这一篇文章讨论一下遇到各种问题怎么去定位解决。本章讨论的场景主要是通过常规手段,如日志、堆栈、代码二分回溯、线上监控等无法明确定位出来问题时,我们怎么去分析内存问题。在遇到问题时,我们还是优先通过日志、堆栈、代码二分回溯和线上监控等去定位分析问题。

一、进程虚拟内存不足问题解决

    目前来说,很多应用都已经出了64位的版本,但是不可避免的还会出现很多机型不支持64位。目前我们统计的结果发现32位的占比还高达60%(当然我们主要是做的海外,国内应该相对会好一些)。所以如果您的应用有虚拟内存不足的问题,在目前阶段,还是很有必要去做一下优化。虚拟内存问题我们主要分三步去解决:

1. 内存泄露问题解决

    在任何时候,内存泄露问题都是最先要收敛的,这时候我们可以借助一些线下和线上的手段去定位解决。内存泄露我放在下一篇文章中详细去讨论,在本篇文章中我们不过多去讨论。

2. 不合理的内存占用分析,特别是比较大的内存占用

    在分析此部分问题时,可以通过自动化测试和手动结合的方式去测试:

  •  自动化测试:     在做自动化测试时,可以隔一段时间(如半个小时)或者内存达到一定阈值时(比如vss达到3G时),解析adb shell dumpsys meminfo的结果,将内存占用通过图形绘制出来,同时去dump内存Hprof文件和smaps文件以及当前的页面截图。接下来就是去分析内存异常时的Hprof文件和smaps文件。

image.png

image.png

    在分析的过程中,我们可能要重点关注一下Bitmap、WebView、媒体播放器Player等内存使用大户的情况,同时也需要关注一下是否有其他的异常内存占用。

     如果应用的Native占用内存过高,可以接入微信Matrix工具中的Native内存监控部分,Matrix能监控到C层通过Malloc分配的内存,并将其以文件的形式把堆栈打印出来,此部分数据可以在保存Hprof文件和Smaps文件时,一并导出来,辅助去分析解决内存问题。

  • 手动测试:      在很多时候,手动测试都是最快最有效的方式去定位内存问题的方法。我们可以使用android studio来观察内存占用情况,但是我更倾向于将内存信息打印到应用上面(如下图),通过不断的点击业务的按钮,进入和退出业务,来观察应用的内存情况。在发生异常时将业务的Hprof文件、smaps文件、Matrxi内存堆栈导出来进行分析。

image.png

     如果应用比较小,出现了Native内存问题,也可以借助于mallocDebug工具去定位分析。

此外,在问题定位的过程中,我们可以对业务的进入和退出进行埋点,将应用内各个业务流转的时序图打印出来,可以辅助分析问题。

3. 虚拟内存真的不够用了

     前两步很多时候会耗费开发者大量的精力,当把内存泄露问题、不合理的分配问题等,能力范围内的内存问题都解决了,但虚拟内存问题依然严重,此时您可以试试下边两个工具:

  • 阿里巴巴出品的神器Patrons: 专门为了解决Android 8.0以上,32位内存不足而生的神器,思路比较清奇,并且大小只有20k,很多应用接入都证明了此方案的有效性,具体可以参考github上的介绍文档。

  • 微信团队出品的Matrix: 适配了Patrons没有支持的8.0以下手机,并且添加了其他的优化。接入反馈也是相当的不错。

二、 Java堆内存不足

    此部分问题解决起来相对比较简单,主要关注以下两个方面:

1. 内存泄露问题解决

    下一篇会做比较详细的分析,此处不再赘述。

2. 通过Hprof观察不合理的内存占用

    在出现Java内存不足时,我们可以通过自动化测试或者手动测试,在内存异常时去Dump内存,保存Hprof文件,然后通过Android Studio和MAT去分析Hprof文件,重点关注一下Bitmap的问题以及不合理的内存分配持有情况。分析出来问题去解决即可,此部分相对简单,分析的文章也比较多,我们就不过多的讨论。

三、FD数量超过系统限制

    此类问题一般都是由于FD泄露导致。FD主要包含以下几种类型:

FD类型说明
socket检查网络请求
anon_inode检查HandlerThread 线程 Looper InputChannel
/dev/ashmem检查数据库操作
/data/data/或/data/app/或/storage/emulate/0/检查对应的文件是否打开未关闭

    当线上出现FD问题是,如果日志、堆栈、代码二分等方法无法定位问题,同样我们可以通过自动化和手动分析的方式去定位问题:

  • 自动化测试:同虚拟内存一样,间歇去获取应用FD的数目,记录一下业务流转的场景,以及业务进入和退出时FD的变化情况进行分析。

  • 手动测试:同样我们可以将FD数量在页面顶部打印出来,然后手动去做业务路径的操作,分析业务进入退出后FD的变化。

当定位到泄露的场景后,就可以具体问题具体分析了。

四、线程数超过系统限制

    线程数过多不仅仅会报线程数超出系统限制的OOM,同样线程数过多还会影响应用的Vss、Pss等。

    线程数的观察可以通过自动化测试和手动去分析:

  • 自动化测试:同虚拟内存一样,间歇去获取应用线程的数目,记录一下业务流转的场景,以及业务进入和退出时线程数的变化情况进行分析。

  • 手动测试:同样我们可以将线程数量在页面顶部打印出来,然后手动去做业务路径的操作,分析业务的进入退出后,线程数的变化。     此外,我们也可以借助于Android Studio的CPU分析或者借用xHook去Hook线程创建去观察应用内部的线程数量变化。

遇见线程数超标问题有以下几点需要去做:

1. 收敛应用内部的线程创建

    很多业务在使用线程时,都是直接通过new Thread、new HandlerThread或者new Executors来获取线程的。并且有些时候创建的线程池core size还比较大,并且没有设置销毁时间。此时我们需要梳理应用内部的Thread、HandlerThread、线程池的创建,然后提供应用内部统一的线程池,把业务内部分散的线程都收敛到统一线程池上。

2. 收敛三方SDK的线程数

    三方SDK大多数情况下都没有提供线程池注入的接口,有些内部还会大量创建线程池,完全不考虑接入方的感受,这也是我比较不齿的地方。此类问题收敛起来也比较麻烦。吐槽归吐槽,问题我们还得解决,我们将三方SDK分为代码可控和不可控两种分别来看:

  • 代码可控SDK:此时对SDK代码改造,支持传入线程池,如果接入方没有传入线程池,再用SDK内部的线程池。

  • 代码不可控SDK:首先我们可以督促三方SDK提供传入线程池的接口,最好是三方SDK改造。如果行不通,我们可以使用滴滴Booster的解决方案,在代码编译期间通过Transform替换,将SDK内部的线程池、线程创建都替换成应用公用的线程池。此处有几个注意点:

  1. 做好替换后的风险评估,会不会导致三方SDK出现问题;
  2. 做好白名单管控,避免替换后出现时序问题或者线程同步问题。
  3. 对三方SDK最好提供单独的线程池,不要和内部使用的线程池混用,避免出现三方SDK把线程池跑满或者卡死,从而导致业务不正常的情况。

五、手机物理内存不足

     如果手机物理内存不足导致的OOM比较多,对其他应用我们暂时没有办法处理,我们能做的就是做好我们自己的内存使用管控。做好高中低端机的分类,然后对各等级设备分别做不同的内存策略。对低内存手机的内存使用策略尽量保守,比如Freso解码后图片占用内存的最大值、公用线程池最大的线程数等;对高内存机型,我们可以稍微奔放一些,从而尽可能减少内存的问题出现。最后再次声明,内存并不是使用的越少越好,合理才是最优。

总结

     本篇文章只是对应用内部出现内存问题以后提供一个分析和解决问题的思路,不过多去讨论三方开源库的源码。当然内存问题多种多样,很多时候还是要具体问题具体分析。

参考文章:

juejin.cn/post/705809…

分类:
Android
标签:
分类:
Android
标签:
收藏成功!
已添加到「」, 点击更改