jvm在对对象进行对堆中对象进行回收时,是如何来判断该对象是否可以被回收呢?是通过什么方式判断呢?
- 哪些对象存活着?
- 哪些对象已死亡?
下面让我们来好好聊聊
判断对象是否存活的算法
- 引用计数算法
- 可达性分析算法
引用计数算法
为对象添加一个引用计数器,初始值为0,如果该对象被引用的话,该值就相应的加1,引用失效时,计数器的值就减1,在任何时刻计数器值为0的对象就是不可能再被使用的对象。主流的jvm虚拟机并没有用该算法,主要是因为该算法没法解决对象循环引用的问题。
在HotSpot虚拟机如下举例:
public class JvmTest {
public Object instance = null;
private static final int _1MB = 1024 * 1024;
private byte[] bigSize = new byte[2 * _1MB];
public static void testGC(){
JvmTest A = new JvmTest();
JvmTest B = new JvmTest();
A.instance = B;
B.instance = A;
A.instance = null;
A = null;
B = null;
System.gc();
}
public static void main(String[] args) {
testGC();
}
}
此时打印的GC日志如下所示,说明进行垃圾回收时,A和B被回收了,明显低于2MB。
可达性分析算法
此算法是将GC Root作为引用链的根节点即起始节点开始,根据引用关系向下搜索,如果某个对象到GC Roots间没有任何引用链相连,也就是说此对象不可达,对于不可达的对象就是需要被回收的对象。
可作为gc roots的对象有:
- 调用方法栈中的参数,局部变量,临时变量。
- 方法区中的静态变量,字符串常量池中的引用。
- native方法引用的对象。
- 类加载器,常驻的异常对象。
- 同步锁持有的对象。
- 还有就是支持局部回收算法的收集器中,该回收区域的对象被别的区域对象所引用,那边这些对象也被视为gc root。
引用分类
对于一些对象,当内存空间还足够是,能保留在内存中,如果内存在经过一遍垃圾回收后内存仍然紧张,就会抛弃掉这些对象。
引用可以分为:强引用、软引用、弱引用、虚引用。
- 强引用:最传统的引用定义,程序中大部分引用都是强引用,也是我们平时在开发的时候最常定义的引用,类似Object obj = new Object() 这种引用关系。只要强引用存在,垃圾收集器就永远不会回收掉被引用的对象。
- 软引用:用来描述一些还有用,但是非必须的对象,如果进行一次回收后内存还是会溢出,那再一次回收这些软引用对象,如果还是不够内存,那就会报OOM。SoftReference类可以实现软引用。
- 弱引用:垃圾收集时肯定会回收到该引用所引用的对象,WeakReference类可以实现弱引用。
- 虚引用:很少用。
finalize方法
这个方法怎么说呢,建议大家最好不要用它,引用这个方法不确定性比较大。
垃圾收集器在回收对象时,会将对象至少作两次标记,第一次如果被标记会可回收的对象的话,随后进行一次筛选,就是从这些别标记为可回收的对象当中进行筛选,筛选什么呢,筛选出这些对象当中实现了finalize方法的对象,如果对象实现了该方法,就会被放在一个F-Queue队列当中,由虚拟机创建的一个优先级非常低的线程从队列当中一个一个去执行他们的finalize方法。这时候对象还有机会将自己被别的对象引用,就又复活了。
public class JvmTest {
public static JvmTest SAVE_HOOK = null;
public void isAlive() {
System.out.println("yes, i am still alive:");
}
@Override
protected void finalize() throws Throwable {
super.finalize();
System.out.println("finalize method executed");
JvmTest.SAVE_HOOK = this;
}
public static void main(String[] args) throws InterruptedException {
SAVE_HOOK = new JvmTest();
//对象第一次成功拯救自己
SAVE_HOOK = null;
System.gc();
//暂定几秒等待finalize方法执行
Thread.sleep(500);
if (SAVE_HOOK != null){
SAVE_HOOK.isAlive();
}else {
System.out.println("no , i am dead");
}
//继续想自救,但是失败了
SAVE_HOOK = null;
System.gc();
Thread.sleep(500);
if (SAVE_HOOK != null){
SAVE_HOOK.isAlive();
}else {
System.out.println("no , i am dead");
}
}
}
可以看出来,执行了finalize方法后对象还没有被回收,但是后面还是被回收了,是因为finalize方法只会被执行一次。
看电视的时候刑场上会有一幕就是,犯人被斩的时候,会有一个人骑马冲出来说刀下留人,然后没斩成,完了后面还被斩,还说刀下留人,但是没有,还是斩。