Hunter的博客

读《深入理解Java虚拟机》-垃圾收集器

读书笔记

对象生死

  • 引用计数算法

    • 优点:实现简单,判断效率也高
    • 缺点:很难解决对象之间互相循环引用的问题
    • Java虚拟机里面没有选用引用计数算法来管理内存
  • 可达性分析算法

    • 主流的商业程序语言(Java、C#)的主流实现中,都是通过可达性分析来判定对象是否存活的

    • 基本思路:通过一系列的称为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的
      可达性分析算法判断对象是否可用
      上图中,对象object 5、object 6、object 7 虽然互相有关联,但是他们到GC Roots是不可达的,所以他们将会被判定为是可回收的对象

    • 在Java语言中,可作为GC Roots的对象包括下面几种:

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

    分为强引用、软引用、弱引用、虚引用

    • 强引用:类似“Object obj = new Object()”这类的引用,只要强引用还存在,垃圾收集器永远不会回收掉被引用的对象

    • 软引用:描述一些还有用但并非必须的对象。在系统将要发生内存溢出之前,将会把这些对象列进回收范围之中进行第二次回收。在JDK1.2之后,提供了SoftReference类来实现软引用

    • 弱引用:描述非必须对象的,被弱引用关联的对象只能生存到下一次垃圾收集发生之前。当垃圾收集器工作时,无论当前内存是否足够,都会回收掉被弱引用关联的对象。在JDK1.2之后,提供了WeakReference类来实现弱引用

    • 虚引用:也称为幽灵引用或幻影引用,是最弱的一种引用关系。无法通过虚引用来取得一个对象实例。为一个对象设置虚引用关联的唯一目的就是能在这个对象被收集器回收时收到一个系统通知。在JDK1.2之后,提供了PhantomReference类来实现虚引用

垃圾收集算法

  • 标记-清除算法

    最基础的收集算法,分为“标记”和“清除”两个阶段:首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象;后续的收集算法都是基于该算法的不足而进行改进得到的,主要不足有两方面:

    1. 效率问题:标记和清除两个过程的效率都不高
    2. 空间问题:标记清除后会产生大量不连续的内存碎片,空间碎片太多可能会导致以后在程序运行过程中需要分配较大对象时,无法找到足够的连续内存而不得不提前触发另一次GC操作

      标记清除算法示意图

  • 复制算法

    • 为了解决标记-清除算法的效率问题而出现

    • 原理:将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这块的内存使用完了,就将还存活的对象复制到另外一块上去,然后再把已使用过的内存空间一次清理掉。这样内存分配时就不用考虑内存碎片等复杂情况了。

    • 商业虚拟机都采用这种收集算法来回收新生代

      复制算法示意图

  • 标记-整理算法

    • 针对老年代采用的收集算法

    • 标记过程与“标记-清除”算法一样

    • 原理:先标记所有待回收的对象,然后将所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存

      标记整理算法示意图

  • 分代收集算法

    • 当前商业虚拟机的垃圾收集都采用这种算法

    • 思路:根据对象存活周期的不同将内存划分为几块,把java堆分为新生代和老年代,然后针对各个年代的特点采用最合适的收集算法。在新生代,每次GC都有大批对象死去,只有少量存活,就选用复制算法。老年代对象存活率高、没有额外空间对他进行分配担保,就必须使用“标记-清除”或“标记-整理”算法来进行回收

垃圾收集器

HotSpot虚拟机的垃圾收集器
上图展示了7种作用不同分代的收集器,如果两个收集器之间存在连线,就说明他们可以搭配使用。虚拟机所处的区域,则表示它是属于新生代收集器还是老年代收集器

  • serial收集器

    • 单线程收集器
    • 在进行垃圾收集时,必须暂停其他所有的工作线程,直到它收集结束
    • 对于运行在Client模式下的虚拟机来说是一个很好的选择
  • parnew收集器

    • serial收集器的多线程版本
  • parallel scavenge收集器

    • 使用复制算法的收集器,又是并行的多线程收集器
    • 吞吐量优先收集器
  • serial old收集器

    • serial收集器的老年代版本
    • 单线程收集器,使用标记-整理算法
  • parallel old收集器

    • parallel scavenge收集器的老年代版本
    • 多线程收集器,使用标记-整理算法
  • cms收集器

    • CMS(Concurrent Mark Sweep)是一种以获取最短回收停顿时间为目标的收集器

    • 整个过程分为4个步骤

      1. 初始标记(CMS initial mark)
        停掉所有工作线程,仅仅是标记下GC Roots能直接关联到的对象,速度很快

      2. 并发标记(CMS concurrent mark)
        进行GC Roots Tracing的过程,耗时最长的过程之一

      3. 重新标记(CMS remark)
        停掉所有工作线程,为了修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间一般会比初始标记阶段稍长一些,但远比并发标记的时间短

      4. 并发清除(CMS concurrent sweep)
        耗时最长的过程之一

        由于整个过程中耗时最长的并发标记和并发清除过程收集器线程都可以与用户线程一起工作,所以总体上来说CMS收集器的内存回收过程是与用户线程一起并发执行的

    • cms是一款优秀的收集器,并发收集、低停顿;也有如下缺点:

      1. 对CPU资源非常敏感
      2. 无法处理浮动垃圾
      3. 因为是基于标记-清除算法实现,所以会有大量空间碎片产生
  • G1收集器

    • 当今收集器技术发展最前沿成果之一,面向服务端应用的垃圾收集器
    • 与其他GC收集器相比,G1具备如下特点:

      • 并行与并发
        G1能充分利用多CPU、多核环境下的硬件优势来缩短Stop-The-World停顿的时间,部分其他收集器原本需要停顿Java线程执行的GC动作,G1收集器仍然可以通过并发的方式让Java程序继续执行

      • 分代收集

      • 空间整合
        G1运作期间不会产生内存空间碎片,收集后能提供规整的可用内存

      • 可预测的停顿