JVM原理之垃圾回收算法及垃圾收集器

6. 垃圾回收算法

6.1 如何确定对象是垃圾

要想进行垃圾回收,必须知道哪些对象是垃圾,那怎么知道呢?

6.1.1 引用计数法

  • 如果一个对象被引用,就说明该对象不是垃圾;如果一个对象没有任何指针对其引用,它就是垃圾。

  • 缺点:互相引用的对象将永远不会被回收。如:对象A中有对象B的引用,B也亦然;

    JVM原理之垃圾回收算法及垃圾收集器

6.1.2 可达性分析

那什么是可达性分析呢?那些东西可以作为GCRoot呢?

  • 从GCRoot开始,可以连接的对象不会被回收,不能连接到即不可达的对象会被回收。

  • 可以作为GCRoot的对象的有 :

    • 静态变量所引用的对象

    • 虚拟机栈中本地变量引用对象

    • 常量池中常量,也就是方法区的常量

    • 本地方法栈中变量引用的对象

    • Thread

    • 类加载器

      如下图所示,浅绿色标识对象为不可达对象,会确定为垃圾。

  • 从GCRoot 出发,能链接到的类不会回收,连接不到即不可达的对象会被回收。
    JVM原理之垃圾回收算法及垃圾收集器

6.2 垃圾回收算法

前面我们了解了是如何确定对象时垃圾的方法,那JVM是如何进行垃圾回收算法的呢?它具体有哪几种垃圾回收算法?

6.2.1 复制回收算法

什么是复制回收算法呢?

  • 将内存分为大小相等的两块,只有其中一方存放数据;如下图所示:
    JVM原理之垃圾回收算法及垃圾收集器

  • 当存放数据的一方内存空间用完了,将还存活的对象复制到另一方 ,然后把已经使用过的内存清理干净。
    JVM原理之垃圾回收算法及垃圾收集器

优点:清理效率高;

缺点:会浪费空间,空间利用率低

6.2.2 标记清除算法

什么是标记清除算法?

  • 标记阶段

    找出内存中需要清理的对象,然后进行标记 JVM原理之垃圾回收算法及垃圾收集器

  • 清除阶段

    将标记出来的对象进行清除
    JVM原理之垃圾回收算法及垃圾收集器

  • 缺点

    • 标记会扫描整个内存空间比较耗时。
    • 有内存碎片(标记清除后会有不连续的内存碎片,空间碎片太多如果后续有大对象需要空间,无法找到连续的内存空间,进而不得不提前触发GC。)

6.2.3 标记清除压缩算法

那什么是标记清除压缩算法(也叫做标记整理算法)呢?

  • 标记阶段

    找出内存中需要清理的对象,然后进行标记
    JVM原理之垃圾回收算法及垃圾收集器

  • 清除压缩阶段(整理阶段)

    将标记出来的对象进行清除,并将存活的对象向一端移动
    JVM原理之垃圾回收算法及垃圾收集器

缺点:标记阶段 和 清除压缩阶段比较耗时。

6.2.4 分代收集算法

上面我们了解到了三种垃圾回收算法,那么我们在堆内存中使用哪个呢?

  • Young区:复制回收算法(因为Young区对象生命周期短,效率高)
  • Old区:标记清除或者标记清除压缩(Old区对象生命存活时间长,复制来复制去没必要,不如标记后进行处理)

7.垃圾收集器

前面我们知道了各种垃圾算法,而垃圾收集器是对于垃圾回收算法的实现,那JVM中具体有哪些垃圾收集器呢?
JVM原理之垃圾回收算法及垃圾收集器

7.1 Serial收集器

Serial收集器是最基本最悠久的收集器,曾经(JDK1.3)是虚拟机新生代垃圾收集的唯一选择。

它是单线程收集器,只会使用一个CPU或者一条线程去完成垃圾回收,并且在垃圾回收的时候会暂停别的线程

  • 优点:简单高效,有很高的单线程收集效率;
  • 缺点:收集过程中暂停所有线程
  • 使用算法:复制算法
  • 适用区域:年轻代- Young区
  • 使用标准:单CPU,单机程序,内存小
    • -XX:+UseSerialGC
  • 图解Serial垃圾回收器
    JVM原理之垃圾回收算法及垃圾收集器

7.2 ParNew收集器

相当于Serial收集器的多线程版本

  • 优点:多CPU下,比Serial效率高

  • 缺点:收集过程暂停所有应用程序线程,单CPU时Serial效率比它高

  • 使用算法:复制算法

  • 适用区域:年轻代-Young区

  • 多CPU,希望快速响应,不希望有停顿时间

    • -XX:UseParNewGC 或者-XX:UseParallelGC
  • 图解 ParNew GC
    JVM原理之垃圾回收算法及垃圾收集器

7.3 Parallel Scavenge 收集器

Parallel Scavenge 收集器与 ParNew 收集器唯一的区别是它更关注系统的吞吐量

吞吐量 = 用户代码运行时间 / (用户代码运行时间 + GC时间);如虚拟机一共运行了100分钟,GC时间为1分钟,吞吐量 = (100-1)/ 100 = 99% ; 吞吐量越大,意味着GC时间越短,则用户程序就可以充分利用CPU资源,尽快完成运算任务。

  • 使用标准:多CPU,大的吞吐量
    • -XX:+UseParallelGC

7.4 CMS 收集器

官网对于CMS收集器的解释:https://docs.oracle.com/javase/8/docs/technotes/guides/vm/gctuning/cms.html#concurrent_mode_failure

CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。采用的是标记-清除算法,分为四步

  • 1.初始标记,标记从GCRoot能关联的对象 ,stop the world , 速度很快

  • 2.并发标记,和用户线程一起进行,从GCRoot 往下追踪

  • 3.重新标记, 暂停用户线程,重新标记对象

  • 4.并发清理,用户线程和GC线程一起执行
    JVM原理之垃圾回收算法及垃圾收集器

  • 优点:并发收集、低停顿

  • 缺点:产生大量的空间碎片、并发阶段会降低吞吐量

  • 启用命令:-XX:+UseConcMarkSweepGC

7.5 SerialOld收集器

SerialOld 收集器是 Serial 收集器在老年代的版本,也是单线程的,采用的算法是 标记-清除-压缩算法 ,运行过程和Serial收集器一样。

  • 图解表示 Serial收集器 与 SerialOld收集器 搭配使用效果
    JVM原理之垃圾回收算法及垃圾收集器

7.6 Parallel Old 收集器

Parallel Old 收集器 是 Parallel Scavenge收集器的老年代的版本,使用多线程标记-清除-压缩算法 进行垃圾回收。依旧是吞吐量优先原则

  • 图解表示 Paraller ScavengeS收集器 与 Parallel Old收集器配合使用图解;
    JVM原理之垃圾回收算法及垃圾收集器

7.7 G1收集器

G1收集器通过多种技术实现了高性能和暂停时间目标。

执行过程:堆被划分为一组大小相等的堆区域,每个堆区域都有一个连续的虚拟内存范围。它也会进行全局标记,标记完成后,它就会知道哪些区域大部分为空。那么他在收集的时候会优先收集这些区域。G1使用暂停预测模型来满足用户定义的暂停时间(-XX:MaxGCPauseMillis)目标,并根据指定的暂停时间选择要收集的区域数。

G1首要重点是为运行过程中需要大的堆内存并且GC延迟有要求的应用程序提供解决方案,这意味着堆大小约为6 GB或更大,并且稳定且可预测的暂停时间低于0.5秒。

以下情况可切换为G1收集器:

  • 超过50%的堆内存被实时数据占用
  • 对象分配率或提升率差率很大
  • 希望 减少 程序垃圾收集时间或压缩暂停 时间(大于0.5到1秒)

工作过程分为如下几步:

  • 初始标记,标记下GC Roots能够关联的对象,需要暂停用户线程
  • 并发标记,从GC Root进行可达性分析,找出存活对象,与用户线程并发执行
  • 最终标记,修正在并发标记阶段因为用户程序执行而变动的数据,需要暂停用户线程
  • 筛选回收,对各个Region的回收价值和成本进行排序,根据GC停顿时间制定回收计划。
    JVM原理之垃圾回收算法及垃圾收集器
  • 启用G1: -XX:+UseG1GC ,设置回收停顿时间,-XX:MaxGCPauseMillis=200 ,默认为200毫秒
  • 优点:1.没有内存碎片 ;2.可以控制垃圾回收停顿时间

8 如何选择合适的垃圾收集器

官方地址:https://docs.oracle.com/javase/8/docs/technotes/guides/vm/gctuning/collectors.html#sthref28

  • 优先调整堆的大小让服务器自己来选择
  • 如果内存小于100M,使用串行收集器-XX:+UseSerialGC
  • 如果响应时间比整体吞吐量更重要,并且垃圾收集暂停时间必须保持小于1秒,可以使用-XX:+UseConcMarkSweepGC 或者 选择 -XX:+UseG1GC