引用计数算法
给对象添加一个引用计数器,当有一个地方引用时,计数器+1;引用失效时,计数器-1;任何时刻计数器为0的对象就是不可能在被使用的。
引用计数算法判断对象是否存活,但是很难解决对象之间相互循环引用的问题。
举例:
public class ReferenceCountingGC {
public Object instance= null;
private static final int _1MB = 1024 * 1024;
//这个变量唯一意义就是占点内存,以便能在GC日志中看清楚是否被回收过
private byte [] bjgSize = new byte[5 * _1MB];
private static void testGC(){
ReferenceCountingGC objA = new ReferenceCountingGC();
ReferenceCountingGC objB = new ReferenceCountingGC();
objA.instance = objB;
objB.instance = objA;
objA = null;
objB = null;
//这里发生GC
System.gc();
}
public static void main(String args[]){
testGC();
}
}
运行结果:
日志最开始的GC和Full GC表示垃圾回收的停顿类型;
PSYoungGen中最前面的PS代表垃圾收集器是Parallel Scavenge收集器,回收的区域是新生代(YoungGen)
ParOldGen中最前面的Par代表垃圾收集器是Parallel Old收集器,回收的区域是老年代(OldGen).
方括号内的8K->641K(10240K)中表示GC前该内存区域使用容量->GC后该内存区域已使用容量(该内存区域总容量).
768K->641K(19456K)表示GC前Java堆已使用容量->GC后Java对已使用容量(Java对总容量).
objA和objB发生了双向引用,虽然objA和objB都为null,但是它们的引用计数器的值都不为0,如果虚拟机使用的是引用计数算法,则不会回收。
图中gc前是768k,gc后是641k,说明对象被回收了。侧面说明虚拟机并不是通过计数算法判断对象是否存活的。
根搜索算法
主流的商用程序语言都是使用根搜索算法判定对象是否存活的。思路是通过一系列名为“GC Roots”的对象为起始点,从这些节点向下搜索,搜索走过的路径成为引用链,当一个GC Roots没有任何引用链相连(从GC Roots到这个对象不可达)时,则证明该对象不可用。
GC Roots的对象包括:
- 虚拟机栈(栈帧中本地变量表)中的引用的对象
- 方法区中类静态属性引用的对象
- 方法区中的常量引用的对象
- 本地方法栈中native方法引用的变量
判断可回收的对象(GC Roots不可达)
不可回收的对象
生存还是死亡
当GC Roots不可达时也并非“非死不可”,要真正宣告一个对象死亡,至少两次标记过程:进行根搜索时,当GC Roots不可达时进行第一次标记并进行一次筛选,筛选条件是该对象是否需要执行finalize()方法。如果对象没有覆盖finalize()方法或者已经执行过一次finalize()方法,则不再执行;如果需要执行的话,该对象被放在一个F-Queue队列中,由虚拟机自动建立的低优先级的Finalizer的线程去执行该finalize()方法,finalize()方法是对象逃脱死亡命运的最后一次机会,稍后GC对F-Queue队列进行小规模的标记,如果finalize()中成功解救了自己–重新与引用链上的对象进行关联,譬如把自己赋给某个变量,在第二次标记时将被移除“即将回收”的集合。
finalize()方法能做的所有工作,使用try-fin’ally或其他方式可以做的更好,不建议使用。
举例:
/**
1. 演示了两点
2. 1 .对象可以在被GC时自我拯救
3. 2 .finalize()方法只能被系统自动调用一次
*/
public class FinalizeEscapeGC {
public static FinalizeEscapeGC SAVE_HOOK = null;
@Override
protected void finalize() throws Throwable {
super.finalize();
System.out.println("finalize()方法被执行了!");
FinalizeEscapeGC.SAVE_HOOK = this;
}
public static void main(String[] args) throws InterruptedException {
SAVE_HOOK = new FinalizeEscapeGC();
///第一次自救
SAVE_HOOK = null;
System.gc();
//Finalier线程的优先级比较低,等待一秒
Thread.sleep(1000);
if(SAVE_HOOK == null){
System.out.println("已经被干掉了(第一次)!");
}else{
System.out.println("我TM还活着(第一次)!");
}
//第二次自救
SAVE_HOOK = null;
System.gc();
//Finalier线程的优先级比较低,等待一秒
Thread.sleep(1000);
if(SAVE_HOOK == null){
System.out.println("已经被干掉了(第二次)!");
}else{
System.out.println("我TM还活着(第二次)!");
}
}
}
运行结果:
回收方法区
主要回收:废弃常量和无用的类
-
废弃常量:与堆中的回收类似,系统中没有引用时,如果必要的话会被回收。
-
无用的类:
- 该类所有实例都已经被回收,就是说堆中不存在该类的实例
- 加载该类的ClassLoader已经被回收
- 该类对象的java.lang.Class对象任何地方被引用,无法在任何地方通过反射访问该类的方法。
满足这三个条件可以进行回收。参考《深入理解java虚拟机》