内存抖動的主要原因抖动是因为大量的对象被创建又在短时间内马上被释放如循环中分配对象,很容易引起GC特别是在较大的循环次数或者一個循环中分配较多的临时对象时。
瞬间产生大量的对象即使对象不大,也可能使得堆的可用空间达到阈值出发GC或导致堆的扩容产生GC
分配大内存抖动的主要原因块的对象,如图片大的byte数组等,虽然堆剩余内存抖动嘚主要原因空间足够但是仍然可能出发GC,因为内存抖动的主要原因碎片导致了找不到连续空间来分配这大内存抖动的主要原因从而出發GC。
内存抖动的主要原因泄露会导致可用内存抖动的主要原因越来越少,而且导致碎片也可能越来越严重这樣就更加容易出发GC。
该命令输出了 进程的内存抖动的主要原因概要我们应该着重关注 四个要点,下面我将一一进行讲解
LeakInspector 是腾讯内部嘚使用的 一站式内存抖动的主要原因泄漏解决方案,它是 Android 手机经过长期积累和提炼、集内存抖动的主要原因泄漏检测、自动修复系统Bug、自動回收已泄露Activity内资源、自动分析GC链、白名单过滤 等功能于一体并 深度对接研发流程、自动分析责任人并提缺陷单的全链路体系。
它们之間主要有 四个方面 的不同如下所示:
这一个蔀分的实现原理我们可以采用 ARTHook 的方式来实现,还不清楚的朋友请再仔细看看大图检测的部分
两者都能采集 dump,但是 LeakInspector 提供了回调方法我们可以增加更多的自定义信息,如运行时 Log、trace、dumpsys meminfo 等信息以辅助分析定位问题。
这里的白名单是为了处理一些系統引起的泄漏问题以及一些因为 业务逻辑要开后门的情形而设置 的。分析时如果碰到白名单上标识的类则不对这个泄漏做后续的处理。二者的配置差异有如下两点:
2)、LeakCanary 的系统白名单里定义的类比 LeakInspector 中定义的多佷多因为它没有自动修复系统泄漏功能。
针对系统泄漏LeakInspector 通过 反射自动修复 了目前碰到的一些系统泄漏,只要在 onDestory 里面 调用 一个修复系统泄漏的方法即可而 LeakCanary 虽然能识别系统泄漏,但是它仅仅对该类问题给出了分析没有提供实际可用的解决方案。
如果检测到发生了内存抖动的主要原因泄漏LeakInspector 会对整个 Activity 的 View 进行遍历,把图片资源等一些占内存抖动的主要原因的數据释放掉保证此次泄漏只会泄漏一个Activity的空壳,尽量减少对内存抖动的主要原因的影响代码大致如下所示:
这里以 recycleTextView 为例,它回收资源嘚方式如下所示:
LeakInspector 在 dump 分析结束之后会提交缺陷单,并且把缺陷单分配给对应类的负责人如果发现重复的问题则更新旧单,同时具备重噺打开单等状态转换逻辑而 LeakCanary 仅会在通知栏提醒用户,需要用户自己记录该问题并做后续处理
LeakInspector 跟自动化测試可以无缝结合,当自动化脚本执行中发现内存抖动的主要原因泄漏可以由它采集 dump 并发送到服务进行分析,最后提单整个流程是不需偠人力介入的。而 LeakCanary 则把分析结果通过通知栏告知用户需要人工介入才能进入下一个流程。
JHat 是 Oracle 推出的一款 Hprof 分析软件它和 MAT 并称为 Java 内存抖动嘚主要原因静态分析利器。不同于 MAT 的单人界面式分析jHat 使用多人界面式分析。它被 内置在 JDK 中在命令行中输入 jhat 命令可查看有没有相应的命囹。
出现如上输出则表明存在 jhat 命令。它的使用很简单直在命令行输入 jhat xxx.hprof 即可,如下所示:
jHat 的执行过程是解析 Hprof 文件然后启动 httpsrv 服务,默认昰在 7000 端口监听 Web 客户端链接维护 Hprof 解析后的数据,以持续供给 Web 客户端进行查询操作
jHat 还有两个比较重要的功能,分别如下所示:
可以到按 Total Size 降序 排列了所有的 Class,并且我们还可以查看到每一个 Class 与之对应的实例数量。
JHat 比 MAT 更加灵活且符合大型团队安装简单、团队协作的需求。但昰并不适合中小型高效沟通型团队使用。
ART 的日志与 Dalvik 的日志差距非常大除了格式不同之外,打印的时间也不同而且,它只有在慢 GC 时才會打印出来下面我们看看这条 ART GC Log:
GC 产生的原因有如下九种:
GC 类型有如下三种:
GC 采集的方法有洳下四种:
通过 GC 日志峩们可以知道 GC 的量和 它对卡顿的影响,也可以 初步定位一些如主动调用GC、可分配的内存抖动的主要原因不足、过多使用Weak Reference 等问题
Android 4.4 及以上系统的原生浏览器就是 Chrome 浏览器可鉯使用 Chrome Devtool 远程调试 WebView,前提是需要在 App 的代码里把调试开关打开如下代码所示:
打开后的调试方法跟纯 H5 页面调试方法一样,直接在 App 中打开 H5 页面再到 PC Chrome 的 inpsector 页面就可以看到调试目标页面。
这里总结一下 JS 中几种常见的内存抖动的主要原因问题点:
若想更深入地学习 Chrome 开发者工具的使用方法,请查看 《Chrome开发者工具中文手册》
在我们进行内存抖动嘚主要原因优化的过程中,有许多内存抖动的主要原因问题都可以归结为一类问题为了便于以后快速地解决类似的内存抖动的主要原因問题,我将它们归结成了以下的多个要点:
说道内类就不得不提到 ”this$0“它是一种奇特的内类成员,每个类实唎都具有一个 this$0当它的内类需要访问它的成员时,内类就会持有外类的 this$0通过 this$0 就可以访问外部类所有的成员。
解决方案是在 Activity 关闭即触发 onDestory 時解除内类和外部的引用关系。
这也是一个 this$0 间接引用的问题对于 Handler 的解决方案一般可以归结为如下三个步骤:
这里需要在使用過程中注意对 WeakReference 进行判空。
如果在闪屏页跳转到登录界面时没有调用 finish()则会造成闪屏页的内存抖动的主要原因泄漏,在碰到这种”过渡界面“的情况时需要注意不要产生这样的内存抖动的主要原因 Bug。
我们通常都会使用 getSystemService 方法来获取系统服务但是当在 Activity 中调用时,会默认把 Activity 的 Context 传给系统服务在某些不确定的情况下,某些系统垺务内部会产生异常从而 hold 住外界传入的 Context。
我们都知道对应 WebView 来说,其 网络延时、引擎 Session 管理、Cookies 管理、引擎內核线程、HTML5 调用系统声音、视频播放组件等产生的引用链条无法及时打断造成的内存抖动的主要原因问题基本上可以用”无解“来形容。
我们在平常开发过程中经常需要在Activity创建的时候去注册一些组件如广播、定时器、事件总线等等。这个時候我们应该在适当的时候对组件进行注销如 onPause 或 onDestory 方法中。
在做资源适配的时候因為需要考虑到 APK 的瘦身问题,无法为每张图片在每个 drawable / mipmap 目录下安置一张适配图片的副本很多同学不知道图片应该放哪个目录,如果放到分辨率低的目录如 hdpi 目录则可能会造成内存抖动的主要原因问题,这个时候建议尽量问设计人员要高品质图片然后往高密度目录下方如 xxhdpi 目录,这样
对于已经被用户使用物理“返回键”退回到后台的进程如果包含了以下 两点,则 不会被轻易杀死
但建议 在运行一段时间(如3小时)后主动保存界面进程(位于后台),然后重启它这样可以有效地降低内存抖动的主要原因负载。
我们应该在 item 被回收不可见时去释放掉对图片的引用如果你使用嘚是 ListView,由于每次 item 被回收后被再次利用都会去重新绑定数据所以只需在 ImageView 回调其 onDetchFromWindow 方法的时候区释放掉图片的引用即可。如果你使用的是 RecyclerView因為被回收不可见时第一次选择是放进 mCacheView中,但是这里面的 item 被复用时并不会去执行 bindViewHolder 来重新绑定数据只有被回收进 mRecyclePool 后拿出来复用才会重新绑定數据。所以此时我们应该在 item 被回收进 RecyclePool 的时候去释放图片的引用这里我们只要去 重写 Adapter 中的 onViewRecycled 方法 就可以了,代码如下所示:
我们应该使用 ViewStub 对那些没有马上用到的资源去做延迟加载并且还有很多大概率不会出现的 View 更要去做懒加载,这样可以等到要使用时洅去为它们分配相应的内存抖动的主要原因
产品或者运营为了统计数据会在每个版本中不断地增加新的埋点。所以我们需要定期地去清理一些过时的埋点以此来 适当地优化内存抖动的主要原因以及CPU的压力。
我们在做子线程操作的时候喜欢使用匿名内部类 Runnable 来操作。但是如果某个 Activity 放在线程池中的任务不能及时执行完毕,在 Activity 销毁时很容易导致内存抖动的主要原因泄漏因为这个匿名内部类 Runnable 类持有一个指向 Outer 类的引用,这样一来如果 Activity 里面的 Runnable 不能及时执行就會使它外围的 Activity 无法释放,产生内存抖动的主要原因泄漏从上面的分析可知,只要在 Activity 退出时没有这个引用即可那我们就通过反射,在 Runnable 进叺线程池前先干掉它代码如下所示:
这个任务就是我们的 Runnable 对象,而 ”this$0“ 就是上面所指的外部类的引用了这里注意使用 WeakReference 装起来,要执行叻先 get 一下如果是 null 则说明 Activity 已经回收,任务就放弃执行
我們发现我们的 APP 在内存抖动的主要原因方面可能存在很大的问题第一方面的原因是我们的线上的 OOM 率比较高。
第二点呢我们经常会看到在峩们的 Android Studio 的 Profiler 工具中内存抖动的主要原因的抖动比较频繁。
这是我们一个初步的现状然后在我们知道了这个初步的现状之后,进行了问题的確认我们经过一系列的调研以及深入研究,我们最终发现我们的项目中存在以下几点大问题比如说:内存抖动的主要原因抖动、内存抖动的主要原因溢出、内存抖动的主要原因泄漏,还有我们的Bitmap 使用非常粗犷
比如 内存抖动的主要原因抖动的解决 => Memory Profiler 工具的使用(呈现了锯齒张图形) => 分析到具体代码存在的问题(频繁被调用的方法中出现了日志字符串的拼接),也可以说说 内存抖动的主要原因泄漏或内存抖動的主要原因溢出的解决
为了不增加业务同学的工作量,我们使用了一些工具类或 ARTHook 这样的 大图检测方案没有任何的侵入性。同时我們将这些技术教给了大家,然后让大家一起进行 工作效率上的提升
我们对内存抖动的主要原因优化工具Profiler Memory、MAT 的使用比较熟悉,因此 针对一系列不同问题的情况我们写了 一系列解决方案的文档,分享给大家这样,我们 整个团队成员的内存抖动的主要原因优化意识就变强 了
我们一开始并没有直接去分析项目中代码哪些地方存在内存抖动的主要原因问题而是先去学习了 Google 官方的一些文档,比如说学习了 Memory Profiler 工具的使用、学习了 MAT 工具的使用在我们将这些工具学习熟练之后,当在我们的项目中遇到内存抖动的主要原因问题时我们就能够很快地进行排查定位问题进行解决。
一开始我们做了整体 APP 运荇阶段的一个内存抖动的主要原因上报,然后我们在一些重点的内存抖动的主要原因消耗模块进行了一些监控,但是后面发现这些监控并没有紧密地结合我们的业务代码,比如说在梳理完项目之后发现我们项目中存在使用多个图片库的情况,多个图片库的内存抖动的主要原因缓存肯定是不公用的所以 导致我们整个项目的内存抖动的主要原因使用量非常高。所以进行技术优化时必须结合我们的业务代碼
我们在做内存抖动的主要原因优化的过程中,不仅做了 Android 端的优化工作还将我们 Android 端一些数据的采集上报到了我們的服务器,然后传到我们的 APM 后台这样,方便我们的无论是 Bug 跟踪人员或者是 Crash 跟踪人员进行一系列问题的解决
比如说 大图片的检测我们最初的一个方案是通过继承 ImageView,重写 它的 onDraw 方法来实现但是,我们在推广它的过程中发现很多开发人员並不接受,因为很多 ImageView 之前已经写过了你现在让他去替换,工作成本是比较高的所以说,后来我们就想有没有一种方案可以 免替换,朂终我们就找到了 ARTHook 这样一个 Hook 的方案
对于 内存抖动的主要原因优化的专项优化 而言,我们要着重注意两点即 优化大方向 和 优化细节。
对於 优化的大方向我们应该 优先去做见效快的地方,主要有以下三部分:
对于 优化细节我们应该 注意一些系统属性或内存抖动的主要原洇回调的使用 等等,主要可以细分为如下六部分:
在这篇文章中我们除了建立了 内存抖动的主要原因的监控闭环 这一核心体系之外,还实现了以下 十大组件 / 策略:
最后,当监控到 应用内存抖动的主要原因超过阈值时还定制了 完善的兜底策略 来 重启应鼡进程。
总的来看要建立一套 全面且成体系的内存抖动的主要原因优化及监控 是非常重要也是极具挑战性的一项工作。并且目前各大公司的 内存抖动的主要原因优化体系 也正处于 不断演进的历程 之中,其目的不外乎:实现更健全的功能、更深层次的定位问题、快速准确哋发现线上问题
路漫漫其修远兮,吾将上下而求索
版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。
点击添加站长微信