经典的垃圾回收器
Posted 杂货铺的学习之路
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了经典的垃圾回收器相关的知识,希望对你有一定的参考价值。
经典的垃圾回收器
从不同的角度分析垃圾回收器,可以将 GC 分为不同的类型。
GC 的分类
按照碎片处理方式划分:压缩式垃圾回收器、非压缩式垃圾回收器。
按照工作的内存区间划分:年轻代垃圾回收器、老年代垃圾回收器。
按照工作模式划分:并发式垃圾回收器、独占式垃圾回收器。
图 1 的串行回收默认被应用在客户端的 Client 模式下的 JVM 中。和串行回收相反的是并发回收可以运用多个 CPU 同时执行垃圾回收。不过并行回收仍然和串行回收一样,都是采用独占式,使用了"Stop-the-world"机制。独占式回收器一旦运行,就停止应用中所有的用户线程,直到垃圾回收过程结束。并发式垃圾回收器与之相反,它可以在应用运行时执行垃圾回收。
GC 的性能指标
吞吐量:运行用户代码的时间占总运行时间(程序运行时间 + 内存回收时间)的比例。
暂停时间:执行垃圾回收时,程序的工作线程被暂停的时间。
吞吐量优先,程序能容忍较高的暂停时间。因此高吞吐量的程序有更长的时间基准,快速响应是不必考虑的。
暂停时间优先,意味着尽可能让单次 STW 的时间最短。
如果选择吞吐量优先,那么必然需要降低内存回收的执行频率,就需要 GC 更长的暂停时间来执行内存回收。相反的,选择低暂停时间(低延迟),为了降低每次执行内存回收时的暂停时间,就需要频繁地执行内存回收,但这又引起了程序吞吐量的下降。
现在的标准:在最大吞吐量优先的情况下,降低暂停时间。
7 种经典的垃圾回收器
串行垃圾回收器:Serial、Serial Old。
并行垃圾回收器:ParNew、Parallel Scavenge(简称:Parallel)、Parallel Old。
图 3 表示垃圾回收器之间的组合关系。其中 Serial Old 作为 CMS 出现"Concurrent Mode Failure"失败后的后备预案。红色虚线,在 JDK 8 中声明为废弃,并在 JDK 9 中完全取消了这些组合的支持,即:移除。绿色虚线,JDK 14 中弃用组合。青色虚线,JDK 14 中删除 CMS。
查看默认垃圾回收器相关命令
-XX:+PrintCommandLineFlags
:查看命令行相关参数。
使用命令行指令查看使用的回收器:jinfo -flag 相关垃圾回收器参数 进程ID
。
Serial GC
JDK 1.3 之前新生代唯一的选择。为 HotSpot 中 Client 模式下的默认新生代垃圾回收器。
Serial 采用复制算法、串行回收、STW 机制的方式执行内存回收。
Serial Old 采用串行回收和 STW 机制,内存回收算法采用的是标记-压缩算法。
Serial Old 在 Server 模式下主要有两个用途:Parallel + Serial Old、作为老年代 CMS 收集器的后背方案。
(图 5 :Serial + Serial Old)
优势:简单高效(与其他回收器的单线程比)。在 HotSpot 虚拟机中,使用 -XX:+UseSerialGC
参数指定年轻代和老年代都是用串行回收器。
ParNew GC
ParNew GC 为 Serial GC 的多线程版本。ParNew 除了采用了并行回收的方式执行内存回收外,两者几乎没有任何区别。
(图 6 :
ParNew + Serial Old)
图 6 对于新生代,回收次数频繁,使用并行方式高效。对于老年代,回收次数少,使用串行方式节省资源(切换线程的资源)。除了 Serial 外,只有 ParNew 能与 CMS 配合工作。
-XX:+UseParNewGC
手动指定 ParNew 回收器执行内存回收任务。它表示年轻代使用并行收集器,不影响老年代。
-XX:ParallelGCThreads
现在线程数量,默认开启和 CPU 数量相同的线程数。
Parallel GC(吞吐量优先)
HotSpot 的年轻代中除了 ParNew 是基于并行回收以外,Parallel Scavenge 同样采用了复制算法、并行回收和 STW 机制。
与 ParNew 收集器不同的是,Parallel 的目标是达到一个可控制的吞吐量,他也被称为吞吐量优先的垃圾回收器。
Parallel 在 JDK 1.6 时提供了用于执行老年代的垃圾回收器 Parallel Old,来代替 Serial Old。
Parallel Old 采用标记-压缩算法,同样基于并行回收和 STW 机制。
JDK 8 中,默认的垃圾回收器组合 Parallel + Parallel Old。
-XX:+UseParallelGC
,-XX:+UseParallelOldGC
两者互相激活。
-XX:MaxGCPauseMillis
设置垃圾回收器最大停顿时间,单位毫秒。一般不设置。
-XX:GCTimeRatio
垃圾回收时间占总时间的比例(1/(N+1)),衡量吞吐量的大小。取值范围是(0, 100),默认 99。
-XX:UseAdaptiveSizePolicy
设置 Parallel 的自适应调节策略。这个模式下,年轻代的大小、Eden 和 Survivor 的比例、晋升老年代的对象年龄等参数会自动调整。
CMS GC(低延迟)
CMS(Concurrent-Mark-Sweep)是 HotSpot 虚拟机中第一款并发回收器,它第一次实现让垃圾收集线程与用户线程同时工作。
CMS 收集器的关注点是尽可能缩短垃圾收集时用户线程的停顿时间。
CMS 采用标记-清除算法,并且也会 "Stop-the-world"。
CMS 作为老年代的回收器,无法与 Parallel 配合工作。新生代只能选择 ParNew 或 Serial 中的一个。
(图 7 :CMS GC)
初始标记:这个阶段的主要任务仅仅只是标记出 GC Roots 能直接关联到的对象。
并发标记:从 GC Roots 的直接关联对象开始遍历整个对象图的过程。不需要停顿用户线程,可以与垃圾收集线程一起并发运行。
重新标记:修正并发标记期间,因用户程序继续运作导致标记产生变动的那一部分对象的标记记录。
并发清理:清理删除标记阶段判断的已经死亡的对象,释放内存空间。
尽管 CMS 采用的是并发回收(非抢占式),但是在其初始化标记和再次标记这两个阶段仍然需要执行 STW 机制暂停程序中的工作线程,不过时间不会太长。由于最耗时的并发标记与并发清除阶段不需要暂停工作,所以整体的回收是低停顿的。
由于 CMS 在垃圾回收阶段用户线程没有中断,所以在 CMS 回收过程中,还应该确保应用程序有足够的内存可用。因此 CMS 不能如其他回收器那样等到老年代几乎完全被填满了在进行回收,而是当堆内存使用率达到某一阈值时,就开始回收,确保程序在 CMS 工作中依然有足够的空间支持程序运行。如果 CMS 运行期间预留的内存无法满足程序需要,就会出现一次“Concurrent Mode Failure”失败。这时虚拟机将启动后备预案:临时启动 Serial Old 来对老年代进行垃圾回收,停顿时间就会很长了。
CMS 采用的垃圾回收算法是标记-清除算法,这就意味着将会不可避免的产生一些内存碎片。在 CMS 为新对象分配内存时,将无法使用指针碰撞技术,而只能选择空闲列表执行内存分配。
CMS 的弊端:会产生内存碎片,导致并发清除后,用户线程空用空间不足。在无法分配大对象时,不得不提前触发 Full GC。无法处理浮动垃圾,在并发标记阶段如果产生新的垃圾对象,CMS 无法对这些垃圾对象进行标记,最终会导致这些新产生的垃圾对象没有被及时回收,只能在下一次执行 GC 时释放这些之前未被回收的内存空间。
-XX:+UseConcMarkSweepGC
、-XX:CMSInitiatingOccupanyFraction
:设置堆内存使用率的阈值,JDK 5 默认 68%,JDK 6 后默认 92%。
Garbage First GC
官方给 G1 设定的目标是在延迟可控的情况下获得尽可能高的吞吐量,所以才当前“全功能回收器”的重任与期望。JDK 9 及之后的默认。
G1 是一个并行回收器,它把堆内存分割为很多不相关的区域(Region)物理上不连续,使用不同的 Region 来表示 Eden、Survior 0、Servior 1、老年代。G1 跟踪各个 Region 里面的垃圾堆积的价值大小,在后台维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的 Region。
-
-
并行性,在回收期间,可以有多个 GC 线程同时工作,此时用户线程 STW。
-
-
-
G1 仍然属于分代型垃圾回收器,它还是会区分年轻代、老年代,但从堆的结构上看,它不要求整个代区连续,也不再坚持固定大小和固定数量。
-
将堆空间分为若干个区域,这些区域中包含逻辑上的年轻代和老年代。它同时兼顾年轻代和老年代的回收。
-
-
G1 内存回收是以 Region 作为基本单位的,Region 之间是复制算法。避免内存碎片,有利于程序的长久运行。
-XX:+UseG1GC
、-XX:G1HeapRegionSize
G1 它将整个 Java 堆区划分为约 2048 个大小相同的独立 Region 块,每个 Region 块的大小根据堆空间的实际大小而定,控制在 1 - 32 M 之间且为 2 的 N 次幂。虽然还保留有新生代和老年代的概念。它们是通过 Region 的动态分配方式实现逻辑上的连续。
G1 还新增了一种新的内存区域,叫做 humongous。主要存储大对象,如果超过 1.5 个 Region,就放到 H。
对于堆中的大对象,默认会被分配到老年代,若是它是一个短期存在的大对象,就会对垃圾回收器造成负面影响。为了解决这个问题,G1 划分了一个 humongous 区,它用来存放大对象。如果一个 H 区装不下一个大对象,那么 G1 会寻找连续的 H 区来存储。为了能找到连续的 H 区,有时候不得不启动 Full GC。G1 大多数行为都把 H 区作为老年代的一部分看待。
G1 垃圾回收过程主要有三个环节:年轻代 GC(Young GC)、老年代并发标记过程(Concurrent Marking)、混合回收(Mixed GC)。若是需要,Full GC 还是继续存在,它针对 GC 的评估失败提供了一种失败保护机制,即强力回收。
当年轻代的 Eden 区用尽时开始年轻代回收过程。G1 的年轻代收集是一个并行的独占式回收器。在收集年轻代时,暂停所有的应用程序线程,启动多线程执行年轻代的回收。将年轻代区间存活对象到 Servicor 区或老年区间。当堆内存使用到达默认 45% 时,开始老年代并发标记过程。标记完成马上开始混合回收过程。G1 的老年代不需要整个老年代被回收,一次只需要扫描/回收一部分老年代的 Region 就可以了。
一个 Region 不可能是孤立的,一个 Region 中的对象可能被其他任意 Region 中对象引用,那么再判断对象存活时,是否需要扫描整个 Java 堆才能确保?
为了解决上述问题,不论是 G1 还是其他分代收集器,JVM 都是使用 Remembered Set 来避免全局扫描,每个 Region 都有一个对应的记忆集。
每次 Reference 类型写入时,都会产生一个 Write Barrier 暂时中断操作。
检查写入引用的对象是否在和引用类型再不同的 Region(其他收集器,检查老年代对象是否引用了新生代对象)。如果不同,会通过 CardTable 把相关引用信息记录到引用指向对象所在的 Region 对应的记忆集中。
参考资料
《深入理解Java虚拟机:JVM高级特性与最佳实践(第3版)》周志明
《JVM 从入门到精通(第一部分)》宋红康
以上是关于经典的垃圾回收器的主要内容,如果未能解决你的问题,请参考以下文章
经典垃圾回收器
JVMJava 中的经典垃圾回收器
JVM垃圾回收篇(经典垃圾回收器讲解)
JVM垃圾回收篇(经典垃圾回收器讲解)
JVM系列之经典垃圾回收器(上篇)
经典面试题 | 讲一下垃圾回收器都有哪些?