自动内存管理机制(3)-HotSpot垃圾收集器

自动内存管理机制(3)-HotSpot垃圾收集器

如果说收集算法是内存回收的方法论,那么垃圾收集器就是内存回收的具体实现。

这里讨论的收集器都是JDK1.7(包含JDK1.7)以后的HotSpot虚拟机:

在这里插入图片描述
上半部属于新生代收集器,下半部属于老年代收集器。如果两个收集器之间存在连线,说明他们可以搭配使用。

1. Serial 收集器

  1. 单线程

    只开启一条GC线程进行垃圾回收,并且在垃圾回收过程中停止一切用户线程,从而用户的请求或图形化页面会出现卡顿。

  2. 适合客户端使用

    一般客户端应用所需内存较小,不会创建太多的对象,而且堆内存不大,因此垃圾回收时间比较短,即使在这段时间停止一切用户线程,用户也不会感受到明显的卡顿,因此本垃圾收集器适合客户端使用。

  3. 简单高效

    由于Serial收集器只有一条GC线程,因此避免了线程切换的开销,从而简单高效。

  4. 采用“复制算法”

2. ParNew 收集器

  1. 多线程并行执行

    其实就是Serial 收集器的多线程版本。

    ParNew由多条GC线程并行的进行垃圾清理。但清理过程仍然需要停止一切用户线程。但由于有多条GC线程同时清理,清理速度比Serial有一定的提升。

  2. 适合多CPU的服务器环境

    由于使用了多线程,因此适合CPU较多的服务器环境

  3. 与Serial性能对比

    ParNew和Serial唯一的区别就是使用了多线程进行垃圾回收,在多CPU的环境下性能比Serial会有一定程度的提升;但线程切换需要额外的开销,因此在单CPU环境中表现不如Serial。

  4. 采用“复制算法”

  5. 追求“降低停顿时间”

    和Serial相比,ParNew使用多线程的目的就是缩短垃圾收集时间,从而减少用户线程被停顿的时间。

3. Parallel Scavenge 收集器

Parallel Scavenge和ParNew一样都是多线程、新生代收集器,都使用“复制”算法进行垃圾回收。ParNew收集器追求降低用户线程的停顿时间,因此适合交互式应用,而Parallel Scavenge追求CPU吞吐量,能够在较短的时间内完成指定任务,因此适合没有交互的后台计算。

  1. 什么是吞吐量?

    吞吐量是指用户线程运行时间占CPU总时间的比例

    CPU总时间包括:用户线程运行时间、GC线程运行时间。

    因此,吞吐量越高表示用户线程运行时间越长,从而用户线程能够被快速处理完。

  2. 降低停顿时间的两种方式

    1. 在多CPU环境中使用多条GC线程,从而垃圾回收的时间减少,用户线程停顿时间也减少。
    2. 实现GC线程与用户线程并发执行。
  3. Parallel Scavenge提供的参数

    1. 设置“吞吐量”

      通过参数-XX:GCTimeRadio设置垃圾回收时间占总CPU时间的百分比。

    2. 设置“停顿时间”

      通过参数-XX:MaxGCPauseMillis设置垃圾处理过程最久停顿时间。Parallel Scavenge会根据这个值的大小确定新生代的大小。如果这个值越小,新生代就会越小,从而收集器就能以较短的时间进行一次回收。但新生代变小后,回收的频率就会提高,因此要合理控制这个值。

    3. 启用自适应调节策略

      通过命令-XX:+UseAdaptiveSizePolicy就能开启自适应策略。我们只要设置好堆的大小和MaxGCPauseMillis或GCTimeRadio,收集器会自动调整新生代的大小、Eden和Survior的比例、对象进入老年代的年龄,以最大程度上接近我们设置的MaxGCPauseMillis或GCTimeRadio。

4. Serial Old 收集器

Serial Old是Serial收集器的老年代版本,它同样是一个单线程收集器,使用“标记 - 整理”算法。主要给Client模式(客户端)下的虚拟机使用,如果在Server模式(服务器)下,有以下两种用途:

  1. 在JDK1.5以及之前的版本中与Parallel Scavenge收集器搭配使用
  2. 作为CMS收集器的后备预案,在并发收集发生Concurrent Mode Failure时使用

5. Parallel Old 收集器

Parallel Old 是 Parallel Scavenge 收集器的老年代版本,使用多线程和“标记 - 整理”算法,他们两一般是搭配使用的,追求CPU吞吐量。

6. CMS 收集器

CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。它是基于“标记 - 清除”算法实现的,整个运作过程分为四个步骤:

  1. 初始标记(CMS initial mark)

    停止一切用户线程,仅使用一条初始标记线程对所有与GC ROOTS直接关联的对象进行标记。速度很快。

  2. 并发标记(CMS concurrent mark)

    使用多条并发标记线程并行执行,并与用户线程并发执行。此过程进行可达性分析,标记出所有废弃的对象。速度很慢。

  3. 重新标记(CMS remark)

    停止一切用户线程,并使用多条重新标记线程并行执行,将刚才并发标记过程中新出现的废弃对象标记出来。这个过程的运行时间介于初始标记和并发标记之间。

  4. 并发清除(CMS concurrent sweep)

    只使用一条并发清除线程,和用户线程们并发执行,清除刚才标记的对象。这个过程非常耗时。

CMS的缺点

  1. 吞吐量低

    由于CMS在垃圾收集过程使用用户线程和GC线程并行执行,从而线程切换会有额外开销,因此CPU吞吐量就不如在垃圾收集过程中停止一切用户线程的方式来的高。

  2. 无法处理浮动垃圾,导致频繁Full GC

    由于垃圾清除过程中,用户线程和GC线程并发执行,也就是用户线程仍在执行,那么在执行过程中会产生垃圾,这些垃圾称为“浮动垃圾”。

    如果CMS在垃圾清理过程中,用户线程需要在老年代中分配内存时发现空间不足时,就需要再次发起Full GC,而此时CMS正在进行清除工作,因此此时只能由Serial Old临时对老年代进行一次Full GC。

  3. 使用“标记-清除”算法产生碎片空间

    由于CMS使用了“标记-清除”算法, 因此清除之后会产生大量的碎片空间,不利于空间利用率。不过CMS提供了应对策略:

    • 开启-XX:+UseCMSCompactAtFullCollection

      开启该参数后,每次FullGC完成后都会进行一次内存压缩整理,将零散在各处的对象整理到一块儿。

    • 设置参数-XX:CMSFullGCsBeforeCompaction

      本参数告诉CMS,经过了N次Full GC过后再进行一次内存整理。

7. G1 收集器

7.1. G1的特点

  • 并行与并发:其他收集器需要停顿执行的GC动作,G1仍然可以通过并发的方式让Java程序继续执行。
  • 分代收集:虽然G1不需要和其他收集器配合,但它仍保留了分代的概念。
  • 空间整合:G1是一种“标记 - 整理”算法和“复制”算法整合的收集器,不会产生内存空间碎片。
  • 可预测的停顿

7.2. G1的内存模型

使用G1收集器时,不再划分原有的新生代或者老年代,它将整个Java堆划分为多个大小相等的独立区域(Region),虽然还保留新生代和老年代的概念,但两者之间不再是物理隔离了,它们都是Region的集合。

7.3. G1的运行过程

  1. 初始标记

    标记与GC ROOTS直接关联的对象,停止所有用户线程,只启动一条初始标记线程,这个过程很快。

  2. 并发标记

    进行全面的可达性分析,开启一条并发标记线程与用户线程并行执行。这个过程比较长。

  3. 最终标记

    标记出并发标记过程中用户线程新产生的垃圾。停止所有用户线程,并使用多条最终标记线程并行执行。

  4. 筛选回收

    回收废弃的对象。此时也需要停止一切用户线程,并使用多条筛选回收线程并行执行。

猜你喜欢

转载自blog.csdn.net/yisany_Q/article/details/84184594