憨人笔记之JVM-垃圾回收算法

话很少说,干就完了。算法

在以前咱们说到堆空间是对象实例存放的地方。程序会一致运行,对象也可能一直建立,可是堆的内存空间是有限的,那么如何保证在程序运行过程当中,堆空间一直有足够的内存来建立新的对象呢?垃圾回收,垃圾回收将已经不使用的对象进行回收,释放内存空间,以便在分配新的对象时有足够的内存空间来进行分配。性能

在对堆空间进行垃圾回收以前,首先就是要肯定哪些对象还"存活",哪些对象已经"死去"(也就是不回再被任何途径所使用)。垃圾回收只会针对死去的对象。this

如何断定对象已死(垃圾判断算法)

引用计数器算法(Reference Counting)

引用计数器算法就是在对象中添加一个引用计数器,每当有一个地方使用到该对象时,计数器值就加1,而当引用失效的时候,该计数器值就减1,只要当计数器的值为0的时候,就表示该对象已经再也不被使用。spa

  • 优势线程

    引用计数器算法实现简单,并且效率高。3d

  • 缺点指针

    很难解决对象之间的相互循环引用问题。code

因为其缺点,因此目前主流的虚拟机中都没有选用引用计数器算法来管理内存。那么什么是对象的相互循环引用呢?经过下面代码实例来进行说明cdn

public class Dog {
  private Cat cat;
  // 省略get/set方法
}

public class Cat {
  private Dog dog;
  // 省略get/set方法
}

public static void main(String[] args){
  Dog dog = new Dog();
  Cat cat = new Cat();
  // 对象的相互循环引用
  cat.dog = dog;
  dog.cat = cat;
  
  dog = null;
  cat = null;
}
复制代码

在上述代码中,虽然dog,cat被置为null,也就是再也不使用了,可是dog和cat之间存在相互引用,因此虚拟机并不会回收这两个对象。对象

可达性分析算法

可达性算法的基本思想就是经过一系列被称为"GC Roots"的对象做为起始点,由这些节点开始向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个GC Roots对象没有任何引用链相连时,则证实该对象是不可用的。

在Java中,可做为GC Roots的对象主要包括了如下几种:

  • 栈帧中的本地变量表中的引用的对象
  • 方法区中类静态属性引用的对象
  • 方法区中常量引用的对象
  • 本地方法栈中JNI(Native方法)引用的对象

须要说明的是,即便是在可达性算法中,不可达的对象,并不是就会被标记为"非死不可"的对象。对于一个对象的死亡宣告,至少要经历两次标记的过程。

第一次标记

l对象通过可达性算法分析后,发现没有与GC Roots项链的引用链时,它会被标记一次,同时会进行筛选,筛选的条件就是此对象有没有必要执行finalize方法。若是该对象没有覆盖finalize方法或者说finalize方法已经被虚拟机执行过。那么虚拟机会认为这两种状况没有必要执行finalize方法。

第二次标记

若是对象被断定为有必要执行finalize方法,那么虚拟机会将对象防止在一个F-Queue队列中。同时虚拟机会自动创建一个低优先级的Finalizer线程去执行它。须要注意的是,执行仅仅表示虚拟机会触发这个对象的finalize方法,但并不会保证等待这个对象的运行结束。

finalize方法是对象逃脱死亡命运的最后一次机会,GC会对F-Queue队列中的对象作第二次标记。对象在finalize方法中,若是从新创建与引用链上的任何一个对象关联,例如将本身(this关键字)赋值给某个类变量或者对象的成员变量。那么在第二次标记时,会被移出"即将回收"的集合。

最终流程如上图所示。须要注意的是,对于任何一个对象的finalize方法,都只会被系统自动调用一次,若是对象已经调用过finalize方法以后,那么它的finalize方法就不会被再次执行。在实际开发过程当中,应当避免调用对象的finalize方法。

垃圾收集算法

常见的垃圾收集算法有四类:标记-清除算法、标记-整理算法、复制算法、分代算法。下面分别依次介绍每一种算法的思想。

标记-清除算法

标记-清除算法是垃圾收集算法中最基础的算法。在以前说如何断定一个对象是否存活的算法中,GC Roots会对对象进行标记,而标记清除算法,则是根据GC Roots的标记判断该对象可回收,以下图:

经过途中能够很明显的看到,标记清除算法会带来一个问题,那就是内存碎片化。在说到堆空间的新生代时,也提到过内存的碎片化,其后果就是会影响到程序的性能。

标记-整理算法

标记整理算法的标记过程同标记-清除算法一致,可是后续过程存在差别,标记-整理算法在标记后不是立马对可回收的对象进行回收,而是让存活的对象都向一端移动,而后清除可用边界之外的对象,释放内存空间。

在上图中能够明显的看到,与标记-清除算法不通的是,它并不会产生内存碎片,而是经过整理,使当前可用对象都会保存在一段连续的内存上。

复制算法

在说到堆空间的新生代时,有说到新生对象从Eden区到Survivor区的流转过程,而这个过程正好就是复制算法的实际体现。复制算法会在内存中划分出两块大小相等的区域(假设为A、B),每次只使用其中一块,当A区域满了的时候,会将存活的对象复制一份到B区域,同时清空A区域。只须要移动堆顶的指针,按照顺序分配,也就不用考虑内存碎片的问题了。

分代算法

在目前主流的商业虚拟机中,基本上采用的都是分代算法,分代算法实质上就是根据对象的存活周期不一样划分不一样的区域。通常是把Java堆划分红新生代和老年代。而针对不一样的代采起不一样的收集算法。例如在新生代中,一般会采用复制算法,而在老年代中,由于对象的存活率较高,通常使用标记-整理或标记-清除算法。

总结

本章中主要讲解了垃圾回收的相关的算法。两个方面,一是断定对象是否存活的算法,二是垃圾回收算法,总结以下图:


不怕路歹行不怕大雨淋,心上一字敢 面对个人梦,甘愿来做憨人。 --<憨人>

相关文章
相关标签/搜索