判断对象是否存活
- 引用计数法
- 可达性分析算法
引用计数法:
引用计数法主要为记录一个对象被引用的次数,当引用数大于0,则不会被垃圾回收;对象没被一次引用,则计数加1,对象失效时计数减一;这个方法对jvm回收来说非常高效,但是不会很准确,假如说两个对象互相引用,即使这两个对象没有其他地方应用,也会一直存在,不会被回收。
可达性分析
可达性分析主要由JVM根据GC root为跟,依次向下找其引用的对象,所有在这个引用链上的对象都不会被回收,一些对象即使有引用链,但是其根节点不是GC Root对象,其也会被回收。
GC Root的对象包括以下四种
- 虚拟机栈所引用的对象
- 静态变量所引用的对象
- 常量所引用的对象
- 本地方法栈所引用的对象
对象即使没有在引用链上,也不会一次被判死刑,其会在finalize()方法中有一次escape gc 的机会。
判断一个对象是否有finalize()逃生机会有两种,
- 对象是否重写了finalize()方法
- 对象是否经历过finalize()方法
只有重写finalize()方法并且没有执行过finalize()方法才会有机会拯救自己,下面写一个程序验证。
/**
* 对象重写finalize() escape GC
* @author wangmj
* @since 2019/1/28
*/
public class FinalizeEscapeGc {
public static FinalizeEscapeGc HOOK = null;
public void isAlive() {
System.out.println("i am alive");
}
@Override
protected void finalize() throws Throwable {
System.out.println("finalize executed start");
super.finalize();
FinalizeEscapeGc.HOOK = this;
System.out.println("finalize executed end");
}
public static void main(String[] args) throws InterruptedException {
HOOK = new FinalizeEscapeGc();
//对象没有可达性时,只有一次escape机会,
HOOK = null;
//会首先判断对象是否有必要执行finalize方法
//当对象没有覆盖finalize()方法或者对象没有调用过finalize()方法则认为没有必要执行
System.gc();
Thread.sleep(500);
//由于覆盖了finalize()方法并且第一次调用,所有对象成功escape
if (HOOK != null) {
HOOK.isAlive();
}else {
System.out.println("i am dead");
}
HOOK = null;
System.gc();
Thread.sleep(500);
//对象已经调用一次,则不会再次调用finalize
if (HOOK != null) {
HOOK.isAlive();
}else {
System.out.println("i am dead");
}
}
}
执行结果
finalize executed start
finalize executed end
i am alive
i am dead
垃圾收集算法
- 标记清除算法
步骤:首先遍历查询所有有引用的对象进行标记,第二步遍历堆,把所有未标记的对象进行清除
缺点:会产生内存碎片 - 复制算法
步骤:需要同样大小的内存,先遍历所有的引用对象并进行复制,复制的同时进行碎片的整理,为了解决两倍大小的内存,设置堆内存的时候设置成Eden区和Survivor区域,复制的时候将存活的对象复制到Survivor区域
缺点:需要两倍大小的内存 - 标记-整理算法
步骤;结合上面两种的算法,先遍历所有有引用的对象并对其标记;遍历所有堆中没有被引用的对象 并清除;对内存进行重新整理;
缺点:复杂性提高