深入理解java虚拟机之垃圾收集器-学习笔记2

垃圾收集器

垃圾收集(Garbage Collection, GC)需要完成的3件事情:

  • 哪些内存需要回收?
  • 什么时候回收?
  • 如何回收?
  1. 判断对象是否已死
    在堆里面存放着Java世界中几乎所有的对象实例,垃圾收集器在对堆进行回收前,第一件事情就是要确定这些对象之中哪些还“存活”着,哪些已经“死去”(即不可能再被任何途径使用到的对象)。
  • 引用计数算法
    给对象添加一个引用计数器,每当有一个地方引用它时,计数器值就加1,当引用失效时,计数器值就减1;任何时刻计数器为0的对象就是不可能再被使用的。弊端:很难解决对象之间的相互循环引用的问题。
  • 可达性算法(主流)
    通过一系列为GC Roots的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链,当一个对象到GC Roots没有任何引用链相连时,则证明该对象是不可用的。下图对象object5, object6, object7虽然有互相判断,但它们到GC Roots是不可达的,所以它们将会判定为是可回收对象。
    深入理解java虚拟机之垃圾收集器-学习笔记2
    在Java语言里,可作为GC Roots对象的包括如下几种:
  • 虚拟机栈(栈桢中的本地变量表)中的引用的对象
  • 方法区中的类静态属性引用的对象
  • 方法区中的常量引用的对象
  • 本地方法栈中JNI的引用的对象
  1. 引用
    无论是通过引用计数法判断对象的引用数量,还是通过可达性算法判断对象的应用链是否可达,判断对象是否存活都与“引用”有关。JDK 1.2之后,Java对引用的概念进行了扩充,将应用分为强应用、软应用、弱引用、需引用四种,这四种引用强度依次逐渐减弱。
  • 强引用就是指再程序代码之中普遍存在的,类似Object obj = new Object()这类的引用,只要强引用还存在,垃圾回收器永远不会回收掉被应用的对象。
  • 软引用是用来描述一些还有用但并非必须的对象。对于软引用关联着的对象,在系统将要发生内存溢出(内存空间不足了)异常之前,将会把这这些对象列进回收范围之中进行第二次回收。在JDK1.2之后,提供了SoftReference类来实现软引用。软引用可用来实现内存敏感的高速缓存,比如网页缓存、图片缓存等。使用软引用能防止内存泄露,增强程序的健壮性。
  • 弱引用也是用来描述非必需对象的,但是它的强度比软引用更弱一些,被弱引用关联着的对象只能生存到下一次垃圾收集发生之前。当垃圾收集器工作时,无论当前内存是否足够,都会回收掉只被弱引用关联着的对象。在JDK1.2之后,提供了WeakReference类来实现软引用。
  • 虚引用也称幽灵引用或幻影引用,他是最弱的一种引用关系。一个对象是否有需引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例。为一个对象设置虚引用关联的唯一目的就是能在这个对象被收集器回收之前得到一个系统通知。在JDK1.2之后,提供了PhantomReference类来实现软引用。
  1. 垃圾回收算法
  • 标记-清除算法
    思想:标记清除算法分为“标记”和“清除”两个阶段,首先标记出需要回收的对象,在标记完成后统一回收所有被标记的对象,标记的方法就是可达性分析算法。
    两个不足:1.效率问题:标记和清除的效率都不高,主要是因为内存经过这种算法垃圾收集后变为不规整的内存,标记和清除的效率受到了影响。2.空间问题:因为内存在垃圾收集后会产生大量不连续的内存碎片,导致以后再需要分配较大的对象时找不到连续的内存空间,不得不提前触发另一次垃圾收集。
    深入理解java虚拟机之垃圾收集器-学习笔记2
  • 复制算法
    思想:将内存按容量分为大小相等的两块,每次只使用其中的一块,当这一块内存用完了,就将还存活的对象复制到另一块内存上去,然后吧已经使用过的内存块一次性清理。
    优点:
    使用这样的方法进行垃圾收集后,内存是规整的,所以不用担心内存碎片等问题,并且因为一次性清理一半的内存,效率很高。
    *缺点:
    内存的使用率低,每次只有一半的内存被使用。
    改进:
    因为内存中的对象大部分的寿命都很短,所以并不需要把内存空间分成相等的两份,可以根据实际的经验,把内存按照合适的比例进行分配。在Java默认的虚拟机HotSpot中默认把内存分为了三份,一份是内存较大的Eden空间,其余是两份比较小的Survivor空间,每次使用Eden和一份Survivor空间,当需要进行垃圾回收时,把存活的对象复制到另外一份Survivor空间上,然后清除使用的Eden和Survivor空间的内存,所以内存的使用率默认是90%,但是我们可以使用虚拟机参数-XX:SurvivorRatio=值来自己进行分配,默认是-XX:SurvivorRatio=8。
    分配担保策略:
    当Survivor空间不够用时,需要依赖老年代内存进行分配担保(Handle Promotion)。如果另外一块Survivor上没有足够空间存放上一次新生代收集下来的存活对象,这些对象将直接通过分配担保机制进入老年代。(内存按照对象的年龄分为新生代和老年代,这部分内容后面会讲解,这里只需要有个老年代担保的印象即可)
    深入理解java虚拟机之垃圾收集器-学习笔记2
  • 标记-整理算法
    复制收集算法在对象存活率较高时就要进行较多的复制操作,效率将会变低。更为关键的是,如果不想浪费50%的空间,就需要有额外的空间进行分配担保,以应对被使用的内存中所有对象都100%存活的极端情况,所以在老年代一般不能直接选用这种算法。
    思想: 和标记—清除算法类似,不过在标记了需要清除对象后,并不是直接进行清除,而是让所有的存活对象向一端移动,然后清除掉其余的内存。
    优点:
    使用这样的方法进行垃圾收集后,内存是规整的,所以不用担心内存碎片等问题。
    深入理解java虚拟机之垃圾收集器-学习笔记2
  • 分代收集算法
    当前商业虚拟机的垃圾收集都采用分代收集(Generational Collection)算法,此算法相较于前几种没有什么新的特征,主要思想为:根据对象存活周期的不同将内存划分为几块,一般是把Java堆分为新生代和老年代,这样就可以根据各个年代的特点采用最适合的收集算法。在新生代中,每次垃圾收集时都发现有大批对象死去,只有少量存活,那就选用复制算法,只需要付出少量存活对象的复制成本就可以完成收集。在老年代中,因为对象存活率高、没有额外空间对它进行分配担保,就必须使用“标记-清除”或“标记-整理”算法来进行回收。
  1. 算法实现(具体略)
  • 枚举根节点
  • 安全点
  • 安全区域
  1. 垃圾收集器(具体略)
  • serial收集器
  • parnew收集器
  • parallel scavenge收集器
  • parallel old收集器
  • cms收集器