JVM判断对象存活
引用计数法
所谓引用计数法就是给每一个对象设置一个引用计数器,每当有一个地方引用这个对象时,就将计数器加一,引用失效时,计数器就减一。当一个对象的引用计数器为零时,说明此对象没有被引用,也就是“死 对象”,将会被垃圾回收.。
引用计数法有一个缺陷就是无法解决循环引用问题,也就是说当对象A引用对象B,对象B又引用者对象A,那么此时A,B对象的引用计数器都不为零,也就造成无法完成垃圾回收,所以主流的虚拟机都没有采用这种算法。
在该代码中
obj1和obj2相互引用,除此之外,无其他任何引用,但因为相
互引用对方导致引用计数器都不为0,因此无法通知GC收集器回收它们
public class CountingGC {
public Object obj = null;
public void testGc(){
TestReferenceCountingGC obj1 = new TestReferenceCountingGC();
TestReferenceCountingGC obj2 = new TestReferenceCountingGC();
obj1.obj = obj2;
obj2.obj = obj1;
obj1 = null;
obj2 = null;
System.gc();
}
}
可达性算法(引用链法)
该算法的思想是:从一个被称为GC Roots的对象开始向下搜索,如果一个对象到GC Roots没有任何引用链相连时,则说明此对象不可用。
在java中可以作为GC Roots的对象有以下几种:
虚拟机栈中引用的对象
方法区类静态属性引用的对象
方法区常量池引用的对象
本地方法栈JNI引用的对象
虽然这些算法可以判定一个对象是否能被回收,但是当满足上述条件时,一个对象不一定会被回收。当一个对象不可达GC Root时,这个对象并不会立马被回收,而是出于一个死缓的阶段,若要被真正的回收需要经历两次标记 如果对象在可达性分析中没有与GC Root的引用链,那么此时就会被第一次标记并且进行一次筛选,筛选的条件是是否有必要执行finalize()方法。当对象没有覆盖finalize()方法或者已被虚拟机调用过,那么就认为 是没必要的。
如果该对象有必要执行finalize()方法,那么这个对象将会放在一个称为F-Queue的对队列中,虚拟机会触发一个Finalize()线程去执行,此线程是低优先级的,并且虚拟机不会承诺一直等待它运行完,这是因为如果 finalize()执行缓慢或者发生了死锁,那么就会造成F-Queue队列一直等待,造成了内存回收系统的崩溃。GC对处于F-Queue中的对象进行第二次被标记,这时,该对象将被移除”即将回收”集合,等待回收。
注意: 可以通过覆盖finalize方法来实现对象的“自救”,避免在标记后被回收,但通常不建议这么做;
对象的引用类型可分为:
强引用:只要存在强引用,GC收集器永远不会回收被引用的对象
软引用:非必须的对象,是否回收,要看当前内存情况,如果紧张,则进行回收,否则不回收(在内存溢出前会将这种类型的对象进行第二次回收)
弱引用:被弱引用关联的对象只能生存到下一次GC发生之前。即每次必被回收(弱引用对象只能生存到下次垃圾回收之前)
虚引用(不会对生存时间存在影响,也无法通过它获取 对象,主要目的就是在回收时收到一个系统通知);
finalize自救:
public class FinalizeGC {
public static FinalizeGC obj = null;
Override
protected void finalize() throws Throwable {
super.finalize();
System.out.println("finalize method executed");
//完成自救
FinalizeGC.obj = this;
}
public static void main(String[] args) {
obj = new FinalizeGC();
obj = null;
//自救
System.gc();
}
}
执行结果:finalize method executed
对象可以在GC时完成自救,这种自救只有一次,因为一个对象的finalize方法最多只会被系统自动调用一次
对象分配规则
1.对象优先分配在Eden区,如果Eden区没有足够的空间时,虚拟机执行一次Minor GC。
2.大对象直接进入老年代(大对象是指需要大量连续内存空间的对象)。这样做的目的是避免在Eden区和两个Survivor区之间发生大量的内存拷贝(新生代采用复制算法收集内存)。
3.长期存活的对象进入老年代。虚拟机为每个对象定义了一个年龄计数器,如果对象经过了1次Minor GC那么对象会进入Survivor区,之后每经过一次Minor GC那么对象的年龄加1,知道达到阀值对象进入老年 区。
4.动态判断对象的年龄。如果Survivor区中相同年龄的所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象可以直接进入老年代。
5.空间分配担保。每次进行Minor GC时,JVM会计算Survivor区移至老年区的对象的平均大小,如果这个值大于老年区的剩余值大小则进行一次Full GC,如果小于检查HandlePromotionFailure设置,如果true则只 进行Monitor GC,如果false则进行Full GC。