Android锁屏解锁卡顿优化——原理分析与GC回收

一、问题描述我在使用Android手机 , 解锁屏幕时出现了卡顿现象 , 从解锁到菜单页面需要卡顿3~4秒 。对于这一方面 , 从事Android开发的我决定 , 测试分析解决这一问题 。

Android锁屏解锁卡顿优化——原理分析与GC回收

文章插图
 
二、分析问题【Android锁屏解锁卡顿优化——原理分析与GC回收】我们使用systrace , 我这里是用的ddms录制trace.html , 用chrom打开systrace提供了一些UI Thread耗时cpu耗时等分析图 , 从图上分析可以看出 , 有很长一段时间 , 系统在进行GC , 大约花费3~4s的时间 , 感觉可以查一下问题 , 从网上查询资料 , 知道是系统进行GC的时候是会阻塞当面界面的 , 表现上就是界面卡住了 , 但是GC是虚拟机系统行为 , code无法控制 , 还得需要找源头 , 为何会有这么多垃圾需要回收 。
三、GC回收原理与解析那我们就先来搞懂GC的源头
GC原理将内存中不再被使用的对象进行回收 , GC中用于回收的方法称为收集器 , 由于GC需要消耗一些资源和时间 , JAVA在对对象的生命周期特征进行分析后 , 按照新生代、旧生代的方式来对对象进行收集 , 以尽可能的缩短GC对应用造成的暂停 。
常见GC算法
  • 引用计数
  • 标记清除
  • 标记整理
  • 分待回收(V8用到的)
算法解析1、引用计数法(Reference Counting):在对象中添加一个引用计数器 , 每当一个地方引用它时 , 计数器就加1;当引用失效时 , 计数器就减1;当引用计数为0时就会被回收 。但是它存在一个很大的问题就是循环引用:如下图 , 当实例化A时 , A会持有实例B , B会持有C , C持有A 。这样一来我们就会发现:如果需要回收A , 除了释放初始实例化引用 , 还需要释放C的引用 。但是由于ABC互相引用 , 所以就造成谁也无法释放 。主流的垃圾回收都没有采用这种判断方法 , 因为需要额外的工作来解决它(感兴趣的童鞋可以看看智能指针) 。
Android锁屏解锁卡顿优化——原理分析与GC回收

文章插图
 
“”
可达性分析算法(Reachability Analysis):在JAVA虚拟机中就是通过可达性分析法来判定对象是否存活的 。思路是通过“GC Roots”的对象(可以认为是确定固定存在的对象)作为起始点 , 然后从这些节点开始遍历所有引用链 , 如果某个对象没有GC Roots直接或间接的连接的话 , 这个对象(白色节点)就被认为程序中不再使用可以被回收了 。如下图:
Android锁屏解锁卡顿优化——原理分析与GC回收

文章插图
 
“”
代码示例:const user1 = { age: 11 }const user2 = { age: 12 }const user3 = { age: 13 }const nameList = [user1.age, user2.age, user3.age]function fn() {const num1 = 1const num2 = 2num3 = 3}fn()
当函数调用过后 , num1和num2在外部不能使用 , 引用数为0 , 会被回收 ; num3是挂载在window上的 , 所以不会被回收 ; 上面的user1、user2、user3被nameList引用 , 所以引用数不为0不会被回收 ;
上面无法回收循环应用对象举例:function fn() {const obj1 = {}const obj2 = {}obj1.name = obj2obj2.name = obj1return 'hello world'}fn()// obj1和obj2 , 因为互相有引用 , 所以计数器并不为0 , fn调用之后依旧无法回收这两个对象2、标记清除:其分为“标记”和“清除”两个阶段 。首先标记出所有死亡的对象 , 然后把所有死亡的的对象进行清除操作 。如下图 , 我们可以清楚地看到 , 这种回收算法有一个很大的问题:造成很多的不连续内存碎片 , 这样一来 , 如果需要创建稍微大一点的对象 , 就很可能无法找到足够大的内存空间 。这就需要整个再进行一次标记整理来解决这一问题(耗时耗力) 。
Android锁屏解锁卡顿优化——原理分析与GC回收

文章插图
 
3.标记整理:算法分为”标记-整理-清除“阶段 , 首先需要先标记出存活的对象 , 然后把他们整理到一边 , 最后把存活边界外的内存空间都清除一遍 。这个算法的好处就是不会产生内存碎片 , 但是由于整理阶段移动了对象 , 所以需要更新对象的引用 。


推荐阅读