内存优化
本文于 1290 天之前发表,文中内容可能已经过时。
[TOC]
2895
为什么要做内存优化?
Java虚拟机
Java内存模型
- 虚拟机栈(线程私有):局部变量表、操作数栈、动态链接、方法出口等信息
- 堆(线程共享):实例对象
- 方法区(线程共享):类信息,常量,即时编译器编译后的代码
- 程序计数器(线程私有):字节码行号指示器,记录当前线程执行到多少行
- 本地方法栈(线程私有):和虚拟机栈类似,两者的区别就是虚拟机栈是为虚拟机执行java方法服务,本地方法栈为虚拟机执行native方法服务 。
程序计数器
线程计数器中如果正在执行java方法,计数器记录的是当前指令的地址,
如果是Native方法,计数器记录为空
堆
堆内存 = 新生代(1) + 老年代(2)
- 新生代:复制算法
- 老年代:标记整理算法
方法区
也叫“永久代”,1.8以后将方法区去除了,将方法区移动到直接内存
内存回收主要考虑堆区和方法区的回收,其他部分会根据线程的产生和消亡
个版本区别
- 1.6:运行常量池在方法区
- 1.7:运行常量池在堆中
- 1.8:删除方法区,引入直接内存,元空间概念,方法区中的静态变量被转移到堆中,只有class元数据在元空间。
堆中的老年代和方法区(永久代)是绑定的,无论哪一方满了,都会触发双方的GC回收
问题:
堆和栈的区别:
- 栈:基本数据类型变量(int、short、long、byte、float、double、boolean、char)以及对象的引用变量
堆:存储java对象
2. 堆中的对象对所有线程可见,栈内存只属于一个线程
3. 堆的内存空间远远大于栈为什么删除方法区?
- 启动大小固定,很难调优,容易发生OOM
- 元空间在本地内存中分配,本地内存足够就不会溢出
GC垃圾回收
判断对象是否存活
- 引用计数算法(缺点:循环引用,技数永远不为0)
- 可达性算法(二叉树中向下搜索,不存在引用链则对象不可用)
回收算法
标记清除算法:标记完后对对象进行回收,使用在老年代
缺点:
- 效率不高,标记和清除效率不高
- 差生大量碎片空间,导致空间浪费
复制算法:将可用对象复制到新的连续空间,删除之前的空间
缺点:
- 浪费50%的内存,复制长生存期的对象效率低下,所以该算法使用在新生代
标记整理算法:前期使用标记清除算法,后续使用整理算法,使对象排列称联系空间,使用在老年代
分代收集算法:对数据进行分代,每一代执行不同的回收算法
年轻代分为eden、s0、s1区,分别为8:1:1,年轻代和老年代为1:2
- 元空间的gc:元空间中的类加载器存活,则元空间中元数据也存活
Minor GC : 清理年轻代
Major GC : 清理老年代
Full GC : 清理整个堆空间,包括年轻代和永久代
四大引用介绍
简述
- 强引用:Strong Reference,通常使用的对象方式,gc不会回收
- 软引用:SoftReference,当内存不足时进行回收
- 弱引用:WeakReference,下一次gc时回收
- 虚引用:PhantomReference,任何时候可回收
在内存泄露问题处理上,使用最多的是弱引用,许多源码、框架都是用
eg:
- ThreadLocalMap中存储以ThreadLocal的弱引用为键,具体内容为value
- Glide中缓存使用activeResource,存储的是图片的弱引用
- 解决Handler的内存泄漏使用弱引用
Reference理解
所有的引用都是继承自Reference,以下以WeakReference为例:
1 | public class WeakReference<T> extends Reference<T> { |
其中存在两种构造方法,区别在于是否传入引用队列,如果不传入引用队列,说明只存在一种引用,不需要引用队列成链存储
1 | public abstract class Reference<T> { |
抽象类很简短,可以看出一个关键点,Reference是一个节点,保存next的引用,方法调用都是使用ReferenceQueue方法,直接进入:
1 | private Reference<? extends T> head = null; |
入队方法中,
- 使用synchronized添加锁,入队结束后释放锁,在ReferenceQueue中并不是标准的队列,使用的是Reference节点成链,行成单链表,类似于MessageQueue.
- 如果是Cleaner类,创建一个虚引用节点,即不如队。Cleaner是用来释放非堆内存,所以做特殊处理
SoftReference
1 | public class SoftReference<T> extends Reference<T> { |
由gc管理时间戳
- clock:上一次gc时间
- timestamp:访问get时最近一次的gc时间
回收条件为:clock - timestamp <= free_heap * ms_per_mb
- free_heep为堆空间空闲大小
- ms_per_mb是保留软引用时间/MB
PhantomReference
1 | public class PhantomReference<T> extends Reference<T> { |
虚引用的get方法返回null,不做gc保留
虚引用通过构造方法可以查看是持有对象引用的
总结:所有引用都是继承自Reference基类的,该类是一个链表节点,ReferenceQueue通过这点形成单链表,称之为队列,进行引用管理,所有引用都可以通过Reference的isEnqueue方法判断引用是否存在。
FinalizerReference理解
java堆中创建对象时,如果java类定义了finalize方法,就会新建一个FinalizerReference类,指向这个新建的对象
内存问题
- 内存泄漏:内存没有按照预期在gc时回收
- 内存溢出:内存大小超出指定大小,导致OOM
- 内存抖动:短时间创建大量内存对象,然后回收,导致内存发生锯齿形抖动,内存空间不连续加上碎片会导致更大的空间,最终OOM
内存优化意义
- 减少OOM,提高系统稳定性
- 减少卡顿,提高流畅度
- 减少内存占用,提高应用存活率
- 减少异常发生和代码逻辑隐患
Android内存泄漏
常见内存泄漏
- 匿名内部类持有外部类引用,导致外部类内存泄漏(Handler)
- 单例传入Context导致调用单例方无法被回收。
- 非静态内部类创建静态实例
- 注册与反注册
- 资源对象关闭
- 集和及时清理
内存泄漏检测
- Profiler,Memory Analyzer(MAT)
Android studio自带内存、cpu、网络的变化,可以根据内存变化做具体分析
2. LeakCanary
框架集成,自动检测内存泄漏,生成app,提供内存泄漏栈堆情况
原理:绑定生命周期,对Activity和Fragment来说,在onDestory时将对象放入弱引用队列进行存储,触发gc后,如果还存在,则发生内存泄漏
3. StrictMode(很少用)
一款比较老的工具,ThreadPolicy可以检测主线程是否网络访问,是否读写。VMPolicy检测内存,Activity,Fragment是否泄漏,资源是否正确关闭
内存优化空间
- 不必要的自动装箱
自动装箱就是将基础数据类型转化为对应的复杂类型,在HashMap的增删改查中充满了自动装箱问题,所以尽量避免这中问题,如将HashMap替换为SparseArray和ArrayMap
2. 内存复用
- 资源复用:通用字符串,颜色,布局
- 视图复用:类似于RecyclerView的优化复用
- 对象池:创建对象池,不用重复创建对象,类似于线程池,messae享元模式
- Bitmap对象复用:使用inBitmap属性可以告知Bitmap解码器尝试使用已经存在的内存区域,新解码的bitmap会尝试使用之前那张bitmap在heap中占据的pixel data内存区域。
在App可用内存过低时主动释放内存
在App退到后台内存紧张即将被Kill掉时选择重写Application中 onTrimMemory/onLowMemory 方法去释放掉图片缓存、静态缓存来自保。其他场景优化
- item被回收不可见时释放掉对图片的引用
- ListView:因此每次item被回收后再次利用都会重新绑定数据,只需在ImageView onDetachFromWindow的时候释放掉图片引用即可。
- RecyclerView:因为被回收不可见时第一选择是放进mCacheView中,这里item被复用并不会只需bindViewHolder来重新绑定数据,只有被回收进mRecyclePool中后拿出来复用才会重新绑定数据,因此重写Recycler.Adapter中的onViewRecycled()方法来使item被回收进RecyclePool的时候去释放图片引用。
- 如果使用字符串拼接,尽量使用StringBuilder、StringBuffer(内存抖动)
- 自定义view减少onDraw的耗时和执行次数
- 尽量使用静态内部类
- 尽量使用基础数据类型
- 合适的时候使用软/弱引用
- item被回收不可见时释放掉对图片的引用
线上监控方案
常规监测
- 当内存使用超过80%,使用 Debug.dumpHprofData(String fileName)
获取dump文件回传至服务器,而后手动分析 - LeakCanary集成并带到线上
- 当内存使用超过80%,使用 Debug.dumpHprofData(String fileName)
Probe线上监测工具
LeakInspector
ResourceCanary