什么是垃圾回收机制
垃圾回收机制是jvm(java虚拟机)在空闲时不定时回收无任何引用对象的机制。
引用分为四种
- 强引用:类似于我们直接New了一个对象。这种对象只有引用存在一直不会回收。
- 软引用:就是现在不用,之后用不用不知道的对象,这种对象是当内存不足的时候进行回收。
- 弱引用:比软引用低一级和软引用相似的,只能活到下一次垃圾回收之前。
- 虚引用:一个对象添加了虚引用,作用就是在垃圾回收的时候,能收到系统发来的回收通知。
垃圾回收机制如何判定对象是要被回收的。
通过根可达算法。RootSet,通过RootSet作为根节点去找下一个引用节点。在对象图中将这种从根(RootSet)形成的引用链进行标记代表存活对象。
哪些对象算RootSet对象:
- 活跃的线程
- 方法区中常量引用的对象
- 方法区中类静态属性引用的对象
- 本地方法栈FIN(Native方法):这个其实就是一些用别的语言比如C/C++实现的接口,不过你可以去调用的方法
对此在标记过程中要注意两点
- 标记的时候会暂停应用线程,否则对象图一直在变化无法真正的去遍历。这种暂停应用线程方便JVM去操作的也叫安全点。
- 宣布一个对象真正结束需要通过两步标记: 第一次标记一个对象不是根可达对象并且进行一次筛选,筛选该对象是不是具备必要执行的finalize()方法。
第二次:如果该对象具有finalize()方法。那么这个对象会被放置在一个名为F-queue队列中,稍后由一天虚拟机创建的低优先级的Finalizer线程去执行finalize方法。但是不一定finalize方法里让该对象重新引用链上的一个对象他就能获救,有些对象在finalize方法里磨磨唧唧。也会被回收。
package com.demo;
/*
* 此代码演示了两点:
* 1.对象可以再被GC时自我拯救
* 2.这种自救的机会只有一次,因为一个对象的finalize()方法最多只会被系统自动调用一次
* */
public class FinalizeEscapeGC {
public String name;
public static FinalizeEscapeGC SAVE_HOOK = null;
public FinalizeEscapeGC(String name) {
this.name = name;
}
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!");
System.out.println(this);
FinalizeEscapeGC.SAVE_HOOK = this;
}
@Override
public String toString() {
return name;
}
public static void main(String[] args) throws InterruptedException {
SAVE_HOOK = new FinalizeEscapeGC("leesf");
System.out.println(SAVE_HOOK);
// 对象第一次拯救自己
SAVE_HOOK = null;
System.out.println(SAVE_HOOK);
System.gc();
// 因为finalize方法优先级很低,所以暂停0.5秒以等待它
Thread.sleep(500);
if (SAVE_HOOK != null) {
SAVE_HOOK.isAlive();
} else {
System.out.println("no, i am dead : (");
}
// 下面这段代码与上面的完全相同,但是这一次自救却失败了
// 一个对象的finalize方法只会被调用一次
SAVE_HOOK = null;
System.gc();
// 因为finalize方法优先级很低,所以暂停0.5秒以等待它
Thread.sleep(500);
if (SAVE_HOOK != null) {
SAVE_HOOK.isAlive();
} else {
System.out.println("no, i am dead : (");
}
}
}
jvm中的堆:
- 堆是用来存放实例对象及数组值得区域
- 堆中对象的内存需要等待GC机制回收。
- 堆分为两块,新生代和老年代。
- 堆共享,栈独享。
GC回收机制 And minorGC,majorGC,FullGC
当我们New了一个对象,这个对象就会进入新生代区中的eden区,新生代分为了三块,eden,To,和From,比例是8:1:1。
在这里新生代的gc是minorGC,老年代的GC是MajorGC。
在新创建的对象进入年轻带之后,如果eden区满了之后,就会触发minorGC。
eden区中仍然存活的对象进入To区,并且age+1,之后To判断其中的对象Age超过15就把对象放进老年代区,之后把To仍然存活的对象放入From区,之后对eden区和to区清空。再把To区和From区的名字更换,这样From区就变成了To区, To区变成了From区,From仍然是空的。但是大部分的情况下,如果进入To区的对象足够大,超过了To的容量大小,那么这些对象就进入老年代。当老年代满的时候就会进行majorGC,当出现majorGc一般都会伴有着MinorGc。
在发生minorGc之前通常会伴有一次虚拟机检查老年代生于内存空间是不是大于新生代所有对象的总和,如果大与就执行minorGC, 如果小于则看HandlePromotionFailure设置是否是允许担保失败(不允许则直接FullGC),如果允许那么在检查老年代历代晋升到老年代的对象平均大小和老年代的最大空间比较,如果有足够的空间则进行minor,否则就直接FullGC
GC算法
-
标记-清除算法
标记-清除算法将垃圾回收分为两个阶段:标记阶段和清除阶段。一种可行的实现是,在标记阶段,首先通过根节点,标记所有从根节点开始的可达对象。因此,未被标记的对象就是未被引用的垃圾对象;然后,在清除阶段,清除所有未被标记的对象。
缺点:效率低下,全堆对象的标记和遍历导致停止的时间长。执行算法之后堆内存空间不连续 -
复制算法(年轻代)
将原有的内存空间分为两块,每次只使用其中一块,在垃圾回收时,将正在使用的内存中的存活对象复制到未使用的内存块中,之后,清除正在使用的内存块中的所有对象,交换两个内存的角色,完成垃圾回收。 -
标记-整理算法(老年代)
是让所有存活对象都向一端移动,然后直接清理掉边界以外的内存
标记/整理算法不仅可以弥补标记/清除算法当中,内存区域分散的缺点,也消除了复制算法当中,内存减半的高额代价。
总结:
(1)效率:复制算法>标记/整理算法>标记/清除算法(此处的效率只是简单的对比时间复杂度,实际情况不一定如此)。
(2)内存整齐度:复制算法=标记/整理算法>标记/清除算法。
(3)内存利用率:标记/整理算法=标记/清除算法>复制算法。