前言
我们知道在Android开发中,由于有垃圾回收机制,所以我们不用花费太多的心思在内存分配或释放上,但是这不是说我们要完全忽略。我们知道系统分配给每个应用程序的内存是有限的,这个内存的上限叫做“堆大小”(Heap Size),不同的手机,所分配的大小也不相同,但是总是有一个限度的,在开发应用程序的时候所使用的内存是不能超过这个限制的,一旦超过,就会产生OutOfMemory,因此在做缓存的时候,也要考虑到堆大小的制约。我们这里将要分析的GC机制,就是垃圾回收机制(Garbage Collection),其作用就是回收一些不再使用的对象,释放内存空间。
GC工作原理
GC说白了就是区分出有用的对象和无用的对象,标记无用的对象并对其进行回收。所谓“有用的对象”其实就是存在其他的程序持有此对象的引用,反之则是无用的对象。而标记无用的对象的方法称为“垃圾标记算法”,可分为“引用计数算法”和“根搜索算法”。
1、引用计数算法
“引用计数算法”其实就是每一个对象都维护一个内存字段来统计它被引用的数量,当对象被引用的时候它的引用计数器就加1,引用失效之后就减1,当为0的时候,表示为“无用的对象”,则标记为“垃圾”待下一步的回收。这种标记方式的优点在于它只维护局部的信息,不用扫描全局的对象图就可以释放无用的对象,其效率比较高。但是现在很多主流的Java虚拟机都没有采用这种方法来标记垃圾,主要原因就在于它的弊端,就是引用计数算法不能解决对象之间相互循环引用的问题,如下所示:
//这就导致了GcObject实例1和实例2的计数引用都不为0,不能释放,造成内存泄漏
public static void main(String[] args) {
GcObject obj1 = new GcObject(); //Step1
GcObject obj 2 = new GcObject();//Step2
obj1.instance = obj2; //Step3
obj2.instance = obj1;// //Step4
obj1 = null; //Step5
obj2 = null; //Step6
}
2、根搜索算法
“根搜索算法”是大多数程序语言中使用的算法,其思想就是从一个名为“GC Roots”的对象作为根出发点,通过引用关系遍历对象图,搜索过过的路径称为“引用链”,当一个对象与GC Roots之间没有任何的引用链,即从GC Roots到该对象不可到达,就判定该对象死亡。但是我们必须注意的是这一过程的本质是找出所有存活的对象,把其他的对象认定为“无用”,而不是找出死的对象回收它们占用的空间。
所谓“GC Roots”其实是一组必须活跃的引用,要实现上述的效果的前提就是必须能完整的枚举出所有的GC Roots,否则就可能会标记“失误”,就好比是递归定义的时候,若是只定义了递推项而没有初始项的时候,关系就没有办法成立,必定会出现错误
Java中可作为GC Roots对象的有如下几种:
Java栈中的引用的对象
本地方法栈中JNI引用的对象
方法区中运行时常量池引用的对象
方法区中静态属性引用的对象
运行中的线程
由引导类加载器加载的对象
GC控制的对象
图解“根搜索算法”
这里写图片描述
以GC Roots为根节点,箭头方向为引用方关系方向,每一个蓝色的原点表示的是内存当中的对象
这里写图片描述
GC机制开始时,从GC Roots对象开始检查,当它能够访问到的对象就说明这还是存在引用,应该进行保留,从图中来看就是从GC Roots开始能连接到黄色的原点的都是有用的对象即存在“引用链”,其余的蓝色的原点可标记为“无用”,那么这些无用的对象会立刻被回收吗?即不存在“引用链”的对象会立刻被垃圾回收器回收吗?我们现在来分析一下java对象在虚拟机中的生命周期
3、Java对象在虚拟机中的生命周期
java对象被类加载器加载到虚拟机中,java对象在java虚拟机中有7个阶段
1、创建阶段(Created)
为对象分配存储空间
构造对象
从超类到子类对static成员进行初始化
递归调用超类的构造方法
调用子类的构造方法
2、应用阶段(In Use)
当对象被创建,并分配给变量赋值,状态就切换到了应用阶段。这一阶段的对象至少要具有一个强引用,或者显式的使用软引用、弱引用或者虚引用。
3、不可见阶段(Invisible)
程序中找不到对象的任何强引用,比如程序的执行已经超出了该对象的作用域。在不可见阶段,对象仍可能被特殊的强引 用GC Roots持有着,比如对象被本地方法栈中JNI引用或是被运行中的线程引用等。
4、不可达阶段(Unreachable)
程序中找不到对象的任何强引用,并且垃圾收集器发现对象不可达。
5、收集阶段(Collected)
垃圾收集器已经发现对象不可达,并且垃圾收集器已经准备好要对该对象的内存空间重新进行分配时。这个时候如果该对象重写了finalize方法,则会调用该方法。
6、终结阶段(Finalized)
当对象执行完finalize法后仍然处于不可达状态时,或者对象没有重写finalize方法,则该对象进入终结阶段,并等待垃圾收集器回收该对象空间。
7、对象空间重新分配阶段(Deallocated)
当垃圾收集器对对象的内存空间进行回收或者再分配时,这个对象就会彻底消失。
由此可知不存在“引用链”的对象不会立刻被垃圾回收器回收,一个对象在被垃圾回收之前,至少要经过两次标记过程,当对象和GC Roots之间不存在引用链时,此对象会被第一次标记为不可到达进入收集阶段,此时会进行一次筛选,如果这个对象没有重写finalize()方法,或者finalize()方法已经被虚拟机调用过,那么这个对象将被放置到一个叫做F-Queue的队列中,队列中对象的finalize()方法将由一个虚拟机自动建立的、低优先级的Finalizer线程去执行(不等待执行结束,防止finalize()方法执行缓慢或假死)。如果finalize()执行过程中,对象和GC Roots引用链又连接上了,比如将自己赋值给某个对象的成员变量,那么在GC对F-Queue中对象进行第二次小规模标记的时候,这个对象将被移出“即将回收”的集合,否则进入终结阶段,最终被回收
@Override
protected void finalize() throws Throwable {
super.finalize();
System.out.println("CanReliveObj finalize called !!!");
obj = this;//在finalize方法中复活对象
}