一、Android内存管理机制
1、Java对象的生命周期
Java对象的生命周期经历7个阶段,分别是创建阶段、应用阶段、不可见阶段、不可达阶段、收集阶段、终结阶段、对象控件重新分配阶段。
2、内存回收机制
内存的三个区域
内存会有三个区域,Yong Generation(年轻代)、Old Generation(年老代)、permanent Generation(持久代)。
其中年轻代里面又分为三个区,eden、S0、S1。
内存的处理过程:
1.对象创建后在Eden区域,
2.执行GC时,如果对象仍然存货,则复制到S0区。
3.当S0区满时,改区域存活对象将复制到S1区,然后S0清空,接下来S0和S1角色互换。
4.当第三部达到一定次数后,存活对象将被复制到Old Generation。
5.当这个对象在Old Generation区域挺溜的时间达到一定程度时,它会被移动到Old Generation,最后积累一定时间再移动到Permaent Generation区域,Permaent Generation区域也存放一些静态文件。
GC回收的一些算法
Copying算法:扫描出存活的对象,并复制到一块新的完全未使用的控件中,对应于Young Generation,就是在Eden、FromSpace或ToSpace之间copy。
标记算法:扫描出存活对象,然后再回收未标记的对象,回收后对空出的空间要么合并,要么标记出来便于下次分配,以减少内存碎片带来的损耗。年老代对象存活时间较长较稳定,使用标记算法回收。
GC类型
1.kGcCauseForAlloc:在分配内存时内存不够情况下引起的GC,这种情况下GC会stop World。Stop World 是由于并发GC时,其他线程都会停止。
2.kGcCauseBackground:当内存达到一定阈值的时候引发GC,这个时候是一个后台GC,不会引起Stop World。
3.kGcCauseExplicit:显示调用时进行的GC,如果ART打开了这个选项,在system.gc时会进行GC。
其他GC注意事项
1.尽量不去显式调用 system.gc() 减少不必要的系统开销,影响应用的流畅度。
2.尽量减少内存泄露,避免OOM。
二、Android内存泄露
1、什么是内存泄露?
java对象有自己的生命周期,当这个对象不需要再使用时,应该完整地走完生命周期,但因为某些原因,对象虽然已经不再使用,仍然在内存中并没有结束整个生命周期,这就意味着这个对象已经泄露了。
GC会选择一些还存活的对象作为内存遍历的根节点 GC Roots,通过对GC Roots的可达性来判断是否需要回收。
Android系统虚拟机的垃圾回收是通过虚拟机GC机制来实现的。GC会选择一些存活的对象作为内存便利的跟节点GC Roots,通过判断GC Roots的可达性来判断是否需要回收,如上图其中 1 2 3 4直接或间接被GC Roots引用链相连,这类对象被认为还需要使用的对象,就不会被回收。5 6 7将会被回收。到那时这里如果Object4 如果不需要使用的话这时候也不会被回收,就属于内存泄露。
2、常见内存泄露场景以及注意事项
~ 资源型对象未关闭
~ 注册对象未注销
~ 类的静态变量持有大数据对象 如bitmap
~ 费静态内部类的静态实例
~ Handler临时性内存泄露
~ 容器中的对象没有清理造成的内存泄露
3、内存泄露分析工具
leakcanary
三、常见注意事项避免内存消耗过多
1、AutoBoxing自动装箱过程
Integer num=0;
for(int i=0;i<100;i++){
num+=i;
}
这段代码每次循环,虚拟机都必须创建一个新的整数对象,并把它加到其他整数对象前面,创建一个新的整数对象,意味着要消耗更多性能。int只有4字节,而Integer对象有16字节。
2、内容复用
1、有效利用系统自带资源。
2、视图复用,如ViewHolder。
3、对象池。
4、Bitmap对象复用。
3、使用最优的数据类型
1、当对象的数目在1000以内且特别多访问而删除和插入不高的时候尽量用ArrayMap替代HashMap。
2、枚举的最大优点是安全、易读,但是内存消耗是定义常量的三倍以上。可以使用注解方式来检查安全。
3、使用IntDef和StringDef检查类型安全。
4、LruCache建议使用这个缓存机制,但是既不能分配太大,也不能分配太小。
4、图片的内存优化
设置位图规格,使用inSampleSize实现位图缩放和压缩。使用缓存机制等。
四、内存分析工具
1、Memory Monitor
这个是一个我们开发过程中很常用的内存、CPU、网络的分析工具。
界面很直观,左上角有运行的机型和项目包名,然后最直观的动态图,分别是CPU、Memory、NetWork。点进去可以进入的Memory。
这里可以清晰的看到颜色对应区域占用的内存大小。
通过这两张图 内存的大部分信息都能查阅到。我们在操作APP加载图片等操作时候能看到内存上升、和下降,如果操作APP后发现内存不会下降可能就是我们有些对象没有及时释放,也有可能导致内存泄露。
还有对应的模拟GC,查看时间段具体内存信息以及一段时间的内存追踪等工具可以使用。可以定位到具体的代码行。如下图:
这里有一个Shallow size这个属性的概念:
Shallow size就是对象本身占用内存的大小,不包含其引用的对象。常规对象(非数组)的Shallow size有其成员变量的数量和类型决定。数组的shallow size有数组元素的类型(对象类型、基本类型)和数组长度决定。
2、Heap Viewrer
如果是Android Studio的话通过Tools->Android->Android Device Monitor找到这个工具。
进入后选择运行APP的包名然后点击update Heap按钮,这时候会在每次gc时展示数据信息,也可以在后面手动GC,如果在操作页面这时候可能会发现小卡顿,因为在GC时,可能导致其他线程停止工作,这时可以清晰看到表中内存信息:
头部总览视图:
标题 | 含义 |
---|---|
Heap Size | 堆栈分配给APP的内存大小。 |
Allocated | 已分配使用的内存大小。 |
Free | 空闲的内存大小。 |
%Used | Allocated/Heap Size 的使用率。 |
#Object | 对象数量 |
下面详情视图:
标题 | 含义 |
---|---|
free | 空闲的对象 |
data object | 数据对象,Java类类型对象,是最主要的观察对象。 |
class object | java类类型的引用对象。 |
1-byte array(byte[],boolean[]) | 一字节的数组对象。 |
2-byte array(short[],char[]) | 两字节的数组对象。 |
4-byte array(object[],int[],float[]) | 4字节的数组对象。 |
8-byte array(long[],double[]) | 8字节的数组对象。 |
non-java object | 非Java对象。 |
每个类型的数据值对应:
标题 | 含义 |
---|---|
Count | 数量 |
Total Size | 总共占用的内存的大小 |
Smallest | 将对象占用内存从小到大排列,排在第一个对象占用内存大小 |
Largest | 将对象占用内存从小到大排列,排在最后一个对象占用的内存大小。 |
Median | 将对象占用内存从小到大排列,排在总监的对象占用的内存大小。 |
Average | 平均值 |