【性能优化】android应用内存优化那些事
android GC机制
内存被管理的环境(例如ART或Dalvik虚拟机)会跟踪每个内存分配。一旦确定程序不再使用内存,便将其释放回堆,而无需程序员干预。在托管内存环境中回收未使用的内存的机制称为垃圾回收。垃圾回收有两个目标:
- 在程序中查找将来无法访问的数据对象;(可达性分析算法)
- 回收那些对象使用的资源。(垃圾回收算法)
回收哪些内存?
引用的分类
- 强引用:代码中普遍存在的,只要强引用还存在,垃圾收集器就不会回收 掉被引用的对象。
- 软引用:SoftReference,用来描述还有用但是非必须的对象,当内存不足 的时候会回收这类对象。
- 弱引用:WeakReference,用来描述非必须对象,弱引用的对象只能生存 到下一次 GC 发生时,当 GC 发生时,无论内存是否足够,都会回收该对象。
- 虚引用:PhantomReference,一个对象是否有虚引用的存在,完全不会 对其生存时间产生影响,也无法通过虚引用取得一个对象的引用,它存在的唯一目的是在这个对象被回收时可以收到一个系统通知。
Android的内存堆是一个分代内存堆,这意味着它会根据预期寿命和所分配对象的大小来跟踪不同的存储buckets。例如,最近分配的对象属于新生代。当一个对象保持活动足够长的时间时,它可以升级为老年代,然后再升级为永久代。
什么时候回收
每一堆生成都有其专用的上限,可以限制那里的对象可以占用的内存量。每当内存区域开始填满时,系统就会执行垃圾回收事件以尝试释放内存。垃圾回收的持续时间取决于其收集对象的一代以及每一代中有多少个活动对象。
即使垃圾回收速度非常快,它仍然会影响应用程序的性能。通常,您无法控制代码中何时发生垃圾回收事件。该系统具有一组运行中的标准,用于确定何时执行垃圾回收。当满足条件时,系统停止执行该过程并开始垃圾回收。如果在动画等密集处理循环的中间或在音乐播放过程中进行垃圾收集,则可能会增加处理时间。这种增加有可能使您应用中的代码执行时间超过建议的以实现高效,流畅的帧渲染的16ms阈值,
如何回收?
可达性分析
可达性分析算法通过一系列称为 GCRoots 的对象作为起始点,从这些节点从上向下搜索,所走过的路径称为引用链,当一个对象没有任何引用链与 GCRoots 连接时就说明此对象不可用,也就是对象不可达。
以上图为例,我们可以知道
- 最下层的两个节点为GC Roots,即GC Tracing的起点
- 中间的一层的对象,可以强引用到达GC根节点,所以被标记为存活
- 最上层的三个对象,无法强引用达到GC根节点,所以无法标记为存活,也就是所谓的垃圾,需要被后续回收掉。
GC Roots 对象通常包括:
-
- 虚拟机栈中引用的对象(栈帧中的本地变量表)
- 方法中类的静态属性引用的对象
- 方法区中常量引用的对象
- Native 方法引用的对象
回收算法
-
标记-清除
标记-清除算法采用从根集合进行扫描,对存活的对象进行标记,标记完毕后,
再扫描整个空间中未被标记的对象,进行回收。标记-清除算法不需要进行对象 的移动,并且仅对不存活的对象进行处理,在存活对象比较多的情况下极为高效, 但由于标记-清除算法直接回收不存活的对象,因此会造成内存碎片。
-
标记-整理
标记-整理算法采用标记-清除算法一样的方式进行对象的标记,但在清除时不
同,在回收不存活的对象占用的空间后,会将所有的存活对象往左端空闲空间移
动,并更新对应的指针。标记-整理算法是在标记-清除算法的基础上,又进行了
对象的移动,因此成本更高,但是却解决了内存碎片的问题。该垃圾回收算法适 用于对象存活率高的场景(老年代)。
-
复制
复制算法将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。 当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已 使用过的内存空间一次清理掉。这种算法适用于对象存活率低的场景,比如新生代。这样使得每次都是对整个半区进行内存回收,内存分配时也就不用考虑内存 碎片等复杂情况。
常见的内存问题有哪些
内存抖动
内存抖动是由于短时间内有大量对象进出新生区导致的,它伴随着频繁的 GC, gc 会大量占用 ui 线程和 cpu 资源,会导致 app 整体卡顿。
定位方法:
使用android studio 自带的Profiler功能
解决方案:
- 避免在循环方法里创建对象,应该把对象创建转移到循环方法外部
- 避免在频繁调用的方法里创建对象,如view的onDraw方法
- 允许复用的情况下,使用对象池进行缓存,例如Handler机制中的Message单链表(obtain方法)
内存泄露
内存泄漏是指堆中存在不再使用的对象,但是垃圾收集器无法将其从内存中删除。
内存泄漏很严重,因为它会占用内存资源并随着时间的推移降低系统性能。如果不加以处理,该应用程序最终将耗尽其资源,最终以致命的java.lang.OutOfMemoryError终止。
堆内存中有两种不同类型的对象-引用对象和未引用对象。引用对象是那些在应用程序中仍持有活跃引用的对象,而未引用的对象则没有被任何应用指向的。
常见内存泄露场景
- 长期持有Activity(Context)引用,例如非静态handler
- 忘记注销监听器
- 非静态内部类持有外部引用
- webview内存泄露
解决方式
- 将非静态内部类改为静态内部类,将引用改为弱引用
- Webview 动态创建(传入application的context)添加到布局中
内存优化方案
- 避免直接加载大图方案,使用Glide或者Picaso第三方方案
- Glide使用RGB565替代ARGB8888色彩模式
- Glide支持三级缓存
- Glide支持多尺寸缓存
- Glide支持Gif图加载
- 压缩图片,压缩png图片或者使用webp、svg格式
- 减少动画帧数
- 使用recycleview替代listview
- 使用更优的数据结构类,例如sparseArray
- 避免使用Enum
- 开启代码混淆减少不必要的类对象
- 减少布局层级
后面补充一下对于leakcanary源码的分析吧
参考文章
- https://blog.csdn.net/nicolelili1/article/details/89219191
- https://droidyue.com/blog/2016/11/23/memory-leaks-in-android/
- https://proandroiddev.com/decrease-memory-usage-of-your-android-app-in-half-a65524d7380b
- https://www.jianshu.com/p/dbe98916a21c