内存未在优化渲染为性能和内存性能下运行,按照推荐位置重新安装

随着技术的发展智能手机硬件配置越来越高,可是它和现在的PC相比其运算能力,续航能力存储空间等都还是受到很大的限制,同时用户对手机的体验要 求远远高于PC嘚桌面应用程序以上理由,足以需要开发人员更加专心去实现和优化渲染为性能和内存你的代码了选择合适的算法和数据结构永远是開发人员最先应该考虑的事 情。同时我们应该时刻牢记,写出高效代码的两条基本的原则:(1)不要做不必要的事;(2)不要分配不必偠的内存

  我从去年开始接触Android开发,以下结合自己的一点项目经验同时参考了Google的优化渲染为性能和内存文档和网上的诸多技术大牛給出的意见,整理出这份文档

  线程池,分为核心线程池和普通线程池下载图片等耗时任务放置在普通线程池,避免耗时任务阻塞線程池后导致所有异步任务都必须等待。

很明显使用流的方式解析效率要高一些,因为DOM解析是在对整个文档读取完后再根据节点层佽等再组织起来。而流的方式是边读取数据边解析数据读取完后,解析也就完毕了在数据格式方面,JSON和Protobuf效率明显比XML好很多XML和JSON大家都佷熟悉。从上面的图中可以得出结论就是尽量使用SAX等边读取边解析的方式来解析数据针对移动设备,最好能使用JSON之类的轻量级数据格式為佳

  设置连接超时时间和响应超时时间。Http请求按照业务需求分为是否可以缓存和不可缓存,那么在无网络的环境中仍然通过缓存的HttpResponse浏览部分数据,实现离线阅读

  对于Android平台上软件的性能测试可以通过以下几种方法来分析效率瓶颈,目前Google在Android软件开发过程中已经引入了多种测试工具包比如Unit测试工程,调试类还有模拟器的Dev Tools都可以直接反应执行性能。

最终duration保存着实际处理该方法需要的毫秒数

5)       线程的使用和同步,Android平台上给我们提供了丰富的多任务同步方法但在深层上并没有过多的比如自旋锁等高级应用,不 过对于Service和 appWidget而言他们實际的产品中都应该以多线程的方式处理,以释放CPU时间对于线程和堆内存的查看这些都可以在DDMS中看到。

移动开发中常见的优化渲染为性能和内存方法多数情况下利用这些优化渲染为性能和内存方法优化渲染为性能和内存后的代码,执行效率有明显的提高内存溢出情况吔有所改善。在实际应用中对程序的优化渲染为性能和内存一定要权衡是否是必须的因为优化渲染为性能和内存可能会带来诸如增加BUG,降低代码的可读性降低代码的移植性等不良效果。希望不要盲目优化渲染为性能和内存请先确定存在问题,再进行优化渲染为性能和內存并且你知道当前系统的性能,否则无法衡量你进行尝试所得到的性能提升希望本文能给大家切实带来帮助。欢迎就上述问题进一步交流如有发现错误或不足,请斧正

不要让昨天的沮丧 让今天的梦想黯然失色 成功的人总是修改方法而不修改目标

下面是内存篇章的学习笔记部汾内容与前面的性能优化渲染为性能和内存典范有重合,欢迎大家一起学习交流!

众所周知与C/C++需要通过手动编码来申请以及释放内存有所不同,Java拥有GC的机制Android系统里面有一个Generational Heap Memory的 模型,系统会根据内存中不同的内存数据类型分别执行不同的GC操作例如,最近刚分配的对象会放在Young Generation区域这个区域的对象通常都是会快速被创建并且很快被销毁回收的,同时这个区域的GC操作速度也是比Old Generation区域的GC操作速度更快的

除了速度差异之外,执行GC操作的时候所有线程的任何操作都会需要暂停,等待GC操作完成之后其他操作才能够继续运行。

通常来说单个的GC並不会占用太多时间,但是大量不停的GC操作则会显著占用帧间隔时间(16ms)如果在帧间隔时间里面做了过多的GC操作,那么自然其他类似计算渲染等操作的可用时间就变得少了。

内存泄漏表示的是不再用到的对象因为被错误引用而无法进行回收

发生内存泄漏会导致Memory Generation中的剩余可鼡Heap Size越来越小,这样会导致频繁触发GC更进一步引起性能问题。

举例内存泄漏下面init()方法来自某个自定义View:

内存作为计算机程序运行最重要嘚资源之一需要运行过程中做到合理的资源分配与回收,不合理的内存占用轻则使得用户应用程序运行卡顿、ANR、黑屏重则导致用户应鼡程序发生 OOM(out of memory)崩溃。抖音作为一款用户使用广泛的产品需要在各种机器资源上保持优秀的流畅性和稳定性,内存优化渲染为性能和内存是必须要重视的环节

本文从抖音 Java OOM 内存优化渲染为性能和内存的治理实践出发,尝试给大家分享一下抖音团队关于 Java 内存优化渲染为性能囷内存中的一些思考包括工具建设、优化渲染为性能和内存方法论。

在未对抖音内存进行专项治理之前我们梳理了一下整体内存指标的絕对值和相对崩溃发现占比都很高。另外内存相关指标在去年春节活动时又再次激增达到历史新高,所以整体来看内存问题相当严峻必须要对其进行专项治理。抖音这边通过前期归因、工具建设以及投入一个双月的内存专项治理将整体 Java OOM 优化渲染为性能和内存了百分之 80

在对抖音的 Java 内存优化渲染为性能和内存治理之前我们先根据平台上报的堆栈异常对当前的 OOM 进行归因,主要分为下面几类:

层崩溃我们對这部分的内存问题也做了针对性优化渲染为性能和内存,主要包括:

治理之后 pthread_create 问题降低到了 0.02‰以下这方面的治理实践会在下一篇抖音 Native 內存治理实践中详细介绍,大家敬请期待本文重点介绍 Java 堆内存治理。

从 Java 堆内存超限的分类来看主要有两类问题:

1. 堆内存单次分配过大/哆次分配累计过大。

触发这类问题的原因有数据异常导致单次内存分配过大超限也有一些是 StringBuilder 拼接累计大小过大导致等等。这类问题的解決思路比较简单问题就在当前的堆栈。

这类问题的问题堆栈会比较分散在任何内存分配的场景上都有可能会被触发,那些高频的内存汾配节点发生的概率会更高比如 Bitmap 分配内存。这类 OOM 的根本原因是内存累积占用过多而当前的堆栈只是压死骆驼的最后一根稻草,并不是問题的根本所在所以这类问题我们需要分析整体的内存分配情况,从中找到不合理的内存使用(比如内存泄露、大对象、过多小对象、夶图等)

工欲善其事,必先利其器从上面的内存治理思路看,工具需要主要解决的问题是分析整体的内存分配情况发现不合理的内存使用(比如内存泄露、大对象、过多小对象等)。

我们从线下和线上两个维度来建设工具:

我们基于 LeakCanary 核心库在线下设计了一套自动分析仩报内存泄露的工具主要流程如下:

图 2.线下自动分析流程

抖音在运行了一段线下的内存泄漏工具之后,发现了线下工具的各种弊端:

  1. 检測出来的内存泄漏过多并且也没有比较好的优先级排序,研发消费不过来历史问题就一直堆积。另外也很难和业务研发沟通问题解决嘚收益大家针对解决线下的内存泄漏问题的 ROI(投入产出比)比较难对齐。

  2. 线下场景能跑到的场景有限很难把所有用户场景穷尽。抖音鼡户基数很大我们经常遇到一些线上的 OOM 激增问题,因为缺少线上数据而无从查起

  3. Android 端的 HPORF 的获取依赖原生的 Debug.dumpHporf,dump 过程会挂起主线程导致明显鉲顿线下使用体验较差,经常会有研发反馈影响测试

  4. LeakCanary 基于 Shark 分析引擎分析,分析速度较慢通常在 5 分钟以上才能分析完成,分析过程会影响进程内存占用

  5. 分析结果较为单一,仅仅只能分析出 Fragment、Activity 内存泄露像大对象、过多小对象问题导致的内存 OOM 无法分析。

正是由于上述一些弊端抖音最早的线下工具和治理流程并没有起到什么太大作用,我们不得不重新审视一下工具建设的重心从线下转成了线上。线上笁具的核心思路是:在发生 OOM 或者内存触顶等触发条件下dump 内存的 HPROF 文件,对 HPROF 文件进行分析分析出内存泄漏、大对象、小对象、图片问题并按照泄露链路自动归因,将大数据问题按照用户发生次数、泄露大小、总大小等纬度排序推进业务研发按照优先级顺序来建立消费流程。为此我们研发了一套基于 HPORF 分析的线下、线上闭环的自动化分析工具 Liko(寓意 ko 内存 Leak 问题)

整体架构由客户端Server 端和核心分析引擎三部分构荿。

在客户端完成 HPROF 数据采集和分析(针对端上分析模式)这里线上和线下策略不同。

线上:主要在 OOM 和内存触顶时通过用户无感知 dump 来获取 HPROF 攵件当 App 退出到后台且内存充足的情况进行分析,为了尽量减少对 App 运行时影响主要通过裁剪 HPROF 回传进行分析,为减轻服务器压力对部分仳例用户采用端上分析作为 Backup。

线下:dump 策略配置较为激进在 OOM、内存触顶、内存激增、监测 Activity、Fragment 泄漏数量达到一定阈值多种场景下触发 dump,并实時在端上分析上传至后台并在本地自动生成 html 报表帮助研发提前发现可能存在的内存问题。

Server 端根据线上回传的大数据完成链路聚合、还原、分配并根据用户发生次数、泄露大小、总大小等纬度促进研发测消费,对于回传分析模式则会另外进行 HPORF 分析

基于 MAT 分析引擎完成内存泄露、大对象、小对象、图片等自动归因,同时支持在线下自动生成 Html 报表

收集过程我们设置了多种策略可以自由组合,主要有 OOM、内存触頂、内存激增、监测 Activity、Fragment 泄漏数量达到一定阈值时触发线下线上策略配置不同。

文件写入进行监控获取 dump 完成时机

为了达到分析过程对于鼡户无感,我们在线上、线下配置了不同的分析时机策略线下在 dump 分析完成后根据内存状态主动触发分析,线上当用户下次冷启退出应用後台且内存充足的情况下触发分析

分析策略我们提供了两种,一种在 Android 客户端分析一种回传至 Server 端分析,均通过 MAT 分析引擎进行分析

端上汾析引擎的性能很重要,这里我们主要对比了 LeakCanary 的分析引擎 Shark 和 Haha 库的 MAT

我们在相同客户端环境对 160M 的 HPROF 多次分析对比发现 MAT 分析速度明显优于 Shark,另外針对 MAT 分析后仍持有统治者树占用内存我们也做了主动释放对比性能收益后采用基于 MAT 库的分析引擎进行分析,对内存泄漏引用链路自动归並、大对象小对象引用链自动分析、大图线下自动还原线上过滤无用链路分析结果如下:

图 7. 内存泄漏链路

对泄漏的 Activity 的引用链进行了聚合汾析,方便一次性解决该 Activity 的泄漏链释放内存

小对象我们对 top 的外部持有对象(OutRefrenrece)进行聚合得到占有小对象最多的链路。

图片我们过滤了图爿库等无效引用且对 Android 8.0 以下的大图在线下进行了还原

为了最大限度的节省用户流量且规避隐私风险,我们通过自研 HPROF 裁剪工具 Tailor 在 dump 过程对 HPROF 进行叻裁剪

除了通过后台根据 GCROOT+ 引用链自动分配研发跟进解决我们常见的内存泄漏外,我们还对系统导致一些内存泄漏进行了分析和修复

如果这个 runQueue 不在主线程那就没有消费的机会。

根据上面的分析发现造成这种内存泄漏需要满足一些条件:

抖音这边大量使用了异步 UI 框架来优化渲染为性能和内存渲染性能框架内部由一个 HandlerThread 驱动,完全符合上述条件针对该问题,我们通过反射获取非主线程的 ThreadLocal在每次异步渲染完主动清理内部的 RunQueue。

图 13. 反射清理流程

大量的内存泄漏如果我们都靠推进研发解决,经常会出现生产大于消费的情况针对这些未被消费的內存泄漏我们在客户端做了监控和止损,将 onDestory 的 Activity 添加到 WeakRerefrence 中延迟 60s 监控是否回收,未回收则主动释放泄漏的 Activity 持有的 ViewTree 的背景图和 ImageView 图片

主要对三種类型的大对象进行优化渲染为性能和内存

  • 全局缓存:针对全局缓存我们按需释放和降级了不需要的缓存,尽量使用弱引用代替强引用关系比如针对频繁泄漏的 EventBus 我们将内部的订阅者关系改为弱引用解决了大量的 EventBus 泄漏。

  • 系统大对象:系统大对象如 PreloadDrawable、JarFile 我们通过源码分析确定主動释放并不干扰原有逻辑在启动完成或在内存触顶时主动反射释放。

  • 动画:用原生动画代替了内存占用较大的帧动画并对 Lottie 动画泄漏做叻手动释放。

图 14. 大对象优化渲染为性能和内存点

小对象优化渲染为性能和内存我们集中在字段优化渲染为性能和内存、业务优化渲染为性能和内存、缓存优化渲染为性能和内存三个纬度不同的纬度有不同的优化渲染为性能和内存策略。

图 15. 小对象优化渲染为性能和内存思路

茬抖音的业务中视频是最核心且通用的 Model,抖音业务层的数据存储分散在各个业务维护了各自视频的 ModelModel 本身由于聚合了各个业务需要的属性很多导致单个实例内存占用就不低,随着用户使用过程实例增长内存占用越来越大对 Model 本身我们可以从属性优化渲染为性能和内存和拆汾这两种思路来优化渲染为性能和内存。

  • 字段优化渲染为性能和内存:针对一次性的属性字段在使用完之后及时清理掉缓存,比如在视頻 Model 内部存在一个 Json 对象在反序列完成之后 Json 对象就没有使用价值了,可以及时清理

  • 类拆分:针对通用 Model 冗杂过多的业务属性,尝试对 Model 本身进荇治理将各个业务线需要用到的属性进行梳理,将 Model 拆分成多个业务 Model 和一个通用 Model采用组合的方式让各个业务线最小化依赖自己的业务 Model,減少大杂烩 Model 不必要的内存浪费

  • 按需加载:抖音这边 IM 会全局保存会话,App 启动时会一次性 Load 所有会话当用户的会话过多时相应全局占用的内存就会较大,为了解决该问题会话列表分两次加载,首次只加载一定数量到内存需要时再加载全部。

  • 内存缓存限制或清理:首页推荐列表的每一次 Loadmore 操作都不会清理之前缓存起来的视频对象,导致用户长时间停留在推荐 Feed 时缓存起来的视频对象过多会导致内存方面的压仂。在通过实验验证不会对业务产生负面影响情况下对首页的缓存进行了一定数量的限制来减小内存压力

上面提到的视频 Model,抖音最早使鼡 Manager 来管理通用的视频实例Manager 使用 HashMap 存储了所有的视频对象,最初的方案里面没有对内存大小进行限制且没有清除逻辑随着使用时间的增加洏不断膨胀,最终出现 OOM 异常为了解决视频 Model 无限膨胀的问题设计了一套缓存框架主要流程如下:

图 16. 视频缓存框架

使用 LRU 缓存机制来缓存视频對象。在内存中缓存最近使用的 100 个视频对象当视频对象从内存缓存中移除时,将其缓存至磁盘中在获取视频对象时,首先从内存中获取若内存中没有缓存该对象,则从磁盘缓存中获取在退出 App 时,清除 Manager 的磁盘缓存避免磁盘空间占用不断增长。

关于图片优化渲染为性能和内存我们主要从图片库的管理和图片本身优化渲染为性能和内存两个方面思考。同时对不合理的图片使用也做了兜底和监控

针对應用内图片的使用状况对图片库设置了合理的缓存,同时在应用 or 系统内存吃紧的情况下主动释放图片缓存

我们知道图片内存大小公式 = 图爿分辨率 * 每个像素点的大小

图片分辨率我们通过设置合理的采样来减少不必要的像素浪费

//请求图片时,传入resize的大小一般直接取View的宽高

而单个像素大小,我们通过替换系统 drawable 默认色彩通道将部分没有透明通道的图片格式由 ARGB_8888 替换为 RGB565,在图片质量上的损失几乎肉眼不可见洏在内存上可以直接节省一半。

图 17. 图片兜底流程

关于对不合理的大图 or 图片使用我们在字节码层面进行了拦截和监控在原生 Bitmap or 图片库创建时機记录图片信息,对不合理的大图进行上报;另外在 ImageView 的设置过程中针对 Bitmap 远超过 view 本身超过大小的场景也进行了记录和上报

图 18. 图片字节码监控方案

是不是解决了 OOM 内存问题就告一段落了呢?作为一只追求极致的团队我们除了解决静态的内存占用外也自研了 Kenzo(Memory Insight)工具尝试解决动態内存分配造成的 GC 卡顿。

  • 类加载准备事件 -> 监控类加载

  • 对象事件 -> 监控内存分配

    • VMObjectAlloc:虚拟机分配一个对象的时候

Kenzo 整体分为两个部分:

  • 输入 Kenzo 监控嘚内存数据

生产端主要以 Java 进行 API 调用,C++完成底层检测逻辑通过 JNI 完成底层逻辑控制。

消费端主要以 Python 完成数据的解析、视图合成以 HTML 完成页面內容展示。

基于动态内存监控我们对最为核心的启动场景的内存分配进行了归因分析优化渲染为性能和内存了一些头部的内存节点分配:

圖 23.启动阶段内存节点归因

另外我们也发现启动阶段存在大量的字符串拼接操作,虽然编译器已经优化渲染为性能和内存成了 StringBuider append但是深入 StringBuider 源碼分析仍在存在大量的动态扩容动作(System.copy),为了优化渲染为性能和内存高频场景触发动态扩容的性能损耗在 StringBuilder

如果你觉得这篇内容对你还蠻有帮助,我想邀请你帮我三个小忙:

点赞转发,有你们的 『点赞和评论』才是我创造的动力。

关注公众号 『 Java斗帝 』不定期分享原創知识。

同时可以期待后续文章ing?

我要回帖

更多关于 优化渲染为性能和内存 的文章

 

随机推荐