垃圾收集器在对堆中的对象进行回收时,需要先判断对象是否还存活,如果确认该对象已经死亡,才开始回收该对象占用的内存。
判断对象是否死亡的方法有如下几种:
引用计数算法
算法描述:给对象中添加一个引用计数器,每当有一个地方引用他时,该计数器就+1,引用失效时,计数器-1,任何时候计数器为0时表示没有该对象不可能再被使用,这种时候就可以确认这个对象已经死亡,可以被回收了。
这个算法实现起来比较简单,效率也高,但是主流java虚拟机中并没有使用该方法,因为不能结局循环引用的问题;如下代码所示:
public class ReferenceCountingGC {
public Object instance = null;
private byte[] bigSize = new byte[2*1024*1024];
public static void main(String[] args) {
ReferenceCountingGC objA = new ReferenceCountingGC();
ReferenceCountingGC objB = new ReferenceCountingGC();
objA.instance = objB;
objB.instance = objA;
objA = null;
objB = null;
System.gc();
}
}
假设objA引用的在堆中的对象为A,objB引用的在堆中的对象为B,
对象A的引用有两个,分别是objA和objB.instance
对象B的引用有两个,分别是objB和objA.instance
当将objA和objB分别设为null之后,对象A和对象B的引用就分别只剩下一个了。
也就是计数器不为0,但是这两个对象在程序中已经不可能被用到了,应该被回收到,由于计数器不能为0,导致不能回收
可达性分析算法
算法描述:通过一系列的GC Roots的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称之为引用链,当一个对象到GC Roots没有任何引用链相连时,证明此对象是不可用的
在java语言中,可作为GC Roots的对象包括下面几种
- 虚拟机栈(栈帧中的本地变量表)中引用的对象
- 方法区中类静态属性引用的对象
- 方法去中常量引用的对象
- 本地方法栈中JNI(即一般说的Native方法)引用的对象
主流的java虚拟机都是使用可达性分析算法来进行判断的
引用
现阶段引用分为4中,如下:
- 强引用(Strong Referenct):类似
Object obj = new Object()
这类的引用,只要强引用还在,垃圾收集器就不会回收掉被引用的对象 - 软引用(Soft Reference):还有用但并非必需的对象,在将要发生内存溢出异常之前,将会把这些对象列进回收范围之中进行第二次回收,如果这次回收还没有足够的内存,才会抛出内存溢出异常
- 弱引用(Weak Reference):用来描述非必需对象的,强度比软引用弱一些,弱引用的对象只能存活到下一次垃圾收集之前。垃圾收集器工作时,无论内存是否足够,都会回收掉这些对象
- 虚引用(Phantom Reference):幽灵引用或幻影引用,最弱的一种引用关系。不会对其引用的对象生存时间构成影响,也无法通过虚引用引用到一个对象,虚引用的目的是能在这个对象被收集器回收时收到一个系统通知,
finalize()
当对象被可达性分析算法计算出为不可达的对象时,对象并不是马上被回收,会先进行一次标记,判断该对象是否需要执行finalize()方法,如果finalize()方法被执行过或者对象没有覆盖finalize()方法,那么不需要执行该方法,反之,则需要执行该方法。如果需要执行finalize()方法的话,该对象将被放入到一个F-Queue的队列之中,稍后会由一个低优先级的线程去处罚它。
如果在finilize()方法中重新与引用链上的任何一个对象进行关联,那么这个方法就不会被回收。但是不建议这么做。
回收方法区
方法区又称之为永久代(Hotspot),主要回收该区域的两部分内容:废弃常量和无用的类
- 废弃常量:如一个字符串“abc”当前系统中没有任何一个String对象是”abc”的,此时这个常量就可以被回收掉
- 无用的类:判定条件比较苛刻,需要满足如下3个条件:
- 类的所有实例被回收
- 该类对象的Class对象没有在任何地方被引用,无法在任何对象通过反射访问该类的方法
- 加载该类的ClassLoader被回收
一般在大量使用反射,动态代理,CGLib等ByteCode框架,动态生成JSP以及OSGi这类频繁自定义的ClassLoader的场景都需要虚拟机能够回收无用的类功能,以保证永久代不会被溢出