1.垃圾回收机制
垃圾回收,也叫GC(Garbage Collection),指的是释放垃圾占用的空间,防止内存泄露。有效的使用可以使用的内存,对内存堆中已经死亡的或者长时间没有使用的对象进行清除和回收。
JVM的内存区域主要分为程序计数器、虚拟机栈、本地方法栈、方法区、堆。其中堆区才是GC作用的区域,其他几个数据区域都不进行GC。对象实例和数组都是在堆上分配的,GC也主要对这两类数据进行回收。
一般,程序使用内存的方式遵循先向操作系统申请一块内存、使用内存、使用完毕之后释放内存归还给操作系统。在传统的C/C++等要求显式释放内存的编程语言中,记得在合适的时候释放内存。而Java等编程语言都提供了基于垃圾回收算法的内存管理机制,不再需要手动释放对象的内存,JVM中的垃圾回收器(Garbage Collector)会自动回收。
Android如今使用的虚拟机名叫Android Runtime,简称Art,而Art的其中一大职责就是负责垃圾回收。Art会在适当的时机触发GC操作,一旦进行GC操作,就会将一些不再使用的对象进行回收。
2.如何判定垃圾
目前主要有两种判定算法:引用计数算法和可达性分析算法。Art采用的是第二种算法。
①引用计数算法
引用计数算法通过在对象头中分配一个空间来保存该对象被引用的次数。如果该对象被其它对象引用,则它的引用计数加1,如果删除对该对象的引用,则它的引用计数减1,当该对象的引用计数为0时,该对象就会被回收。
注意,引用有四种类型分别是强引用、软引用、弱引用和虚引用。引用的类型会影响到垃圾的回收。
(1)强引用:通过new创建一个新对象时返回的引用就是一个强引用,若一个对象通过一系列强引用可到达,它就是强可达的(strongly reachable),那么它就不可能被系统垃圾回收机制回收。
(2)软引用:垃圾回收机制运行时,系统内存空间足够不会被回收,不足够会被回收。软引用和弱引用的区别在于,若一个对象是弱引用可达,无论当前内存是否充足它都会被回收,而软引用可达的对象在内存不充足时才会被回收,因此软引用要比弱引用“强”一些;
(3)弱引用:垃圾回收机制运行时,不管系统内存是否足够,都会被回收。
(4)虚引用:几乎等于没有引用,以至于我们通过虚引用甚至无法获取到被引用的对象。虚引用存在的唯一作用就是当它指向的对象被回收后,虚引用本身会被加入到引用队列中,用作记录它指向的对象已被回收。
下面通过实例来演示和说明:
String obj = new String("Android");
该段代码先创建一个字符串Android,其内存分在堆中,并且这个时候"Android"有一个引用,就是obj,它指向字符串Android。
如果此时将obj设置为null,这时候“Android”字符串的引用次数就为0了,在引用计数垃圾回收中,意味着此时就要进行垃圾回收了。
obj = null;
此时演示的示意图如下所示,即将进行垃圾回收。
引用计数算法有一个致命问题就是不能解决循环引用问题。
②可达性分析算法
可达性算法的原理是以一系列叫做GC Root的对象为起点出发,引出它们指向的下一个节点,再以下个节点为起点,引出此节点指向的下一个结点(这样通过GC Root串成的一条线就叫引用链),直到所有的结点都遍历完毕。如果相关对象不在任意一个以GC Root为起点的引用链中,则这些对象会被判断为垃圾,会被GC回收。
如上图,用可达性算法可以解决Java对象循环引用导致的引用计数法无法回收的问题。因为从GC Root出发没有到达obj5和obj6的有效路径,所以obj5和obj6可以回收。
obj5和obj6对象可被回收,就一定会被GC回收吗?并不是,对象从判定可回收到回收需要经历下面两个阶段:
第一个阶段是可达性分析,分析该对象是否可达。
第二个阶段是当对象没有重写finalize()方法或者finalize()方法已经被调用过,虚拟机认为该对象不可以被救活,因此回收该对象。(finalize()方法在垃圾回收中的作用是,给该对象一次救活的机会)。当发生GC时,会先判断对象是否执行了 finalize方法,如果未执行,则会先执行finalize方法,因此可以在此方法里将当前对象与GC Roots 关联,这样执行finalize方法之后,GC会再次判断对象是否可达,如果不可达,则会被回收,如果可达,则不回收。
注意: finalize方法只会被执行一次,如果第一次执行finalize方法时此对象变成了可达确实不会回收,但如果对象再次被GC,则会忽略finalize方法,对象会被回收!这一点切记!
有了上面垃圾对象的判定,还要考虑一个问题,就是stop the world,因为垃圾回收的时候,需要整个的引用状态保持不变,否则判定是垃圾。所以GC的时候,其他所有的程序执行都要处于暂停状态,卡住了。幸运的是,这个卡顿是非常短的,对程序的影响也是微乎其微,所以GC的卡顿问题由此而来,也是无可避免的。
3.GC Root对象
通过可达性算法,成功解决了引用计数所无法解决的问题-“循环依赖”,只要无法与GC Root建立直接或间接的连接,系统就会判定为可回收对象。现在的关键问题就是,哪些属于GC Root?
在Java语言中,可作为GC Root对象包括以下4种:
①虚拟机栈(栈帧中的局部变量表)中引用的对象
②方法区中类静态属性引用的对象
③方法区中常量引用的对象
④本地方法栈中JNI(即一般说的Native方法)引用的对象
注意:全局变量同静态变量不同,它不会被当作GC Root。
下面分别介绍这4种GC Root:
①虚拟机栈(帧栈中局部变量表)中引用的对象
public class XXX {
public XXX(String name){
}
public static void xx(String[] args){
XXX obj = new XXX("Localtable");
obj = null;
}
}
上面obj即为GC Root,当obj置为null时,XXX对象也断掉了与GC Root的引用链,该对象将被回收。
②方法区中类静态属性引用的对象
public class XXX {
public static XXX instance;
public XXX(String name) {
}
public static void xx(String[] args){
XXX obj = new XXX("Localtable");
obj.instance = new XXX("staticProperty");
obj = null;
}
}
obj为GC Root(虚拟机栈局部变量表),当obj置为null后,经过GC垃圾回收,obj所指向的XXX对象由于无法与GC Root建立