抖音 Android 性能优化系列:Java 内存优化篇( 三 )


文章插图
 
图 8. 大对象链路
大对象不止分析了引用链路,还递归分析了内部 top 持有对象(InRefrenrece)的 RetainedSize 。
小对象 

抖音 Android 性能优化系列:Java 内存优化篇

文章插图
 
图 9. 小对象链路
小对象我们对 top 的外部持有对象(OutRefrenrece)进行聚合得到占有小对象最多的链路 。
图片
抖音 Android 性能优化系列:Java 内存优化篇

文章插图
 
图 10. 图片链路
图片我们过滤了图片库等无效引用且对 Android 8.0 以下的大图在线下进行了还原 。
抖音 Android 性能优化系列:Java 内存优化篇

文章插图
 
回传分析为了最大限度的节省用户流量且规避隐私风险,我们通过自研 HPROF 裁剪工具 Tailor 在 dump 过程对 HPROF 进行了裁剪 。
裁剪过程
抖音 Android 性能优化系列:Java 内存优化篇

文章插图
 
图 11. Tailor 裁剪流程
去除了无用信息
  • 跳过 header
  • 分 tag 裁剪
    • 裁剪无用信息:char[]; byte[]; timestamp; stack trace serial number; class serial number;
    • 压缩数据信息
同时对数据进行 zlib 压缩,在 server 端数据还原,整体裁剪效果:180M--->50M---->13M
优化实践内存泄漏除了通过后台根据 GCROOT+ 引用链自动分配研发跟进解决我们常见的内存泄漏外,我们还对系统导致一些内存泄漏进行了分析和修复 。
系统异步 UI 泄漏根据上传聚合的引用链我们发现在 Android 6.0 以下有一个 HandlerThread 作为 GCROOT 持有大量 Activity 导致内存泄漏,根据引用发现这些泄漏的 Activity 都被一个 Runnable(这里是 Runnable 是一个系统事件 SendViewStateChangedAccessibilityEvent)持有,这些 Runnable 被添加到一个 RunQueuel 中,这个队列本身被 TheadLocal 持有 。
抖音 Android 性能优化系列:Java 内存优化篇

文章插图
 
图 12. HandlerThread 泄露链路
我们从 SendViewStateChangedAccessibilityEvent 入手对源码进行了分析发现它在 notifyViewAccessibilityStateChangedIfNeeded 中被抛出,系统的大量 view 都会在自身的一些 UI 方法(eg: setChecked)中触发该函数 。
抖音 Android 性能优化系列:Java 内存优化篇

文章插图
 
SendViewStateChangedAccessibilityEvent 的 runOrPost 方法会走到我们常用的 View 的 postDelay 方法中,这个方法在当 view 还未被 attched 到根 view 的时候会加入到一个 runQueue 中 。
抖音 Android 性能优化系列:Java 内存优化篇

文章插图
 
这个 runQueue 会在主线程下一次的 performTraversals() 中消费掉 。
抖音 Android 性能优化系列:Java 内存优化篇

文章插图
 
如果这个 runQueue 不在主线程那就没有消费的机会 。
根据上面的分析发现造成这种内存泄漏需要满足一些条件:
  1. view 调用了 postDelay 方法 (这里是 notifyViewAccessisbilityStateChangeIfNeeded 触发)
  1. view 处于 detached 状态
  2. 上述过程是在非主线程里面操作的,ThreadLocal 非 UIThread,持有的 runQueue 不会走 performTraversals 消费掉 。
  1.  
 
抖音这边大量使用了异步 UI 框架来优化渲染性能,框架内部由一个 HandlerThread 驱动,完全符合上述条件 。针对该问题,我们通过反射获取非主线程的 ThreadLocal,在每次异步渲染完主动清理内部的 RunQueue 。
抖音 Android 性能优化系列:Java 内存优化篇

文章插图
 
图 13. 反射清理流程
另外,google 在 6.0 上也修复了 notifyViewAccessisbilityStateChangeIfNeeded 的判断不严谨问题 。
抖音 Android 性能优化系列:Java 内存优化篇

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