一、垃圾回收器

问题一:垃圾回收算法和垃圾回收器有什么关系?
垃圾回收算法是垃圾回收的方法论,垃圾收集器是垃圾回收算法的具体实现
问题二:为什么有这么多种垃圾回收器?
Java虚拟机规范中对垃圾收集器应该如何实现并没有任何规定,
因此不同厂商、不同版本的虚拟机所提供的垃圾收集器都有可能有很大差别
目前为止,还没有完美的收集器出现,Java的应用场景很多,没有万能的收集器能解决所有应用场景,只是针对具体应用选择最合适的收集器,进行分代收集。
问题三:如何查看默认的垃圾回收器?
java -XX:+PrintCommandLineFlags -version

1、垃圾回收器的组合使用
1.Serial(年轻代) + Serial Old(老年代)
- 特点:均为单线程回收,STW(Stop The World)时间简单可控
- 适用场景:客户端模式、资源受限的轻量环境(如低配设备)
2.Parallel Scavenge(年轻代) + Parallel Old(老年代)
- 特点:均为并行多线程回收,以 "吞吐量优先" 为核心目标
- 适用场景:后台计算、批处理任务;是 JDK 8 的默认组合之一
3.ParNew(年轻代) + CMS(老年代)
- 特点:ParNew 是多线程版 Serial,配合 CMS 的 "并发标记清除",主打低延迟
- 适用场景:Web 应用(JDK 7/8 与 CMS 搭配)、B/S 系统(重视响应速度)
4.G1(独立使用)
适用场景:超大堆(>4GB)、需要平衡吞吐与延迟的场景;是 JDK 9 + 的默认回收器
5.ZGC(独立使用)
适用场景:TB 级超大堆、极致低延迟需求(停顿 < 10ms);JDK 15 + 可生产使用
6.Shenandoah(独立使用)
适用场景:红帽 JDK 环境、低延迟需求;与 ZGC 定位类似(竞争关系)
| 类型 | 回收器组合/独立回收器 | 核心特点 | 使用场景 |
|---|---|---|---|
| 分代组合 | Serial + Serial Old | 单线程回收,STW 简单可控 | 客户端模式、资源受限轻量环境 |
| 分代组合 | Parallel Scavenge + Parallel Old | 并行多线程,吞吐量优先 | 后台计算、批处理;JDK 8 默认组合之一 |
| 分代组合 | ParNew + CMS | 多线程 + 并发标记清除,低延迟 | Web 应用(JDK 7/8)、B/S 系统(重视响应速度) |
| 全堆独立回收器 | G1 | 分 Region 回收,平衡吞吐与延迟 | 超大堆(>4GB);JDK 9 + 默认回收器 |
| 全堆独立回收器 | ZGC | 并发整理,亚毫秒停顿(<10ms) | TB 级超大堆、极致低延迟;JDK 15 + 生产可用 |
| 全堆独立回收器 | Shenandoah | 低停顿,与 ZGC 竞争 | 红帽 JDK 环境、低延迟需求 |
Parallel Scavenge和ParNew两个年轻代的并行回收器的区别


高吞吐量和低延迟分别指什么?


2、串行回收器
2.1新生代串行回收器
串行收集器是JDK中最基本的垃圾回收器之一。
串行回收器主要有两个特点:
1.它仅仅使用单线程进行垃圾回收
2.它是独占式的垃圾回收
在串行收集器进行垃圾回收时,Java应用程序中的线程都需要暂停,等待垃圾回收的完成。如图所示,在串行回收器运行时,应用程序中的所有线程都停止工作,进行等待。这种现象称之为"Stop-The-World"。它将造成非常糟糕的用户体验,在实时性要求较高的应用场景中,这种现象往往是不能被接受的。

新生代串行处理器 使用复制算法,实现相对简单、逻辑处理特别高效、且没有线程切换的开销。在诸如单CPU处理器等硬件平台不是特别优越的场合,它的性能表现可以超过并行回收器和并发回收器。
并行回收器和并发回收器分别指什么?

使用-XX:+UseSerialGC参数可以指定使用新生代串行收集器和老年代串行收集器。
当虚拟机在Client模式下运行时,它是默认的垃圾收集器。
代码案例:
java
public class GCDemo {
public static void main(String[] args) {
System.out.println("=====GCDemo,Hello====");
try {
String str = "GCDemo";
while (true){
str += str + new Random().nextInt(77777777) + new Random().nextInt(88888888);
str.intern();
}
}catch (Throwable e){
e.printStackTrace();
}
}
}

结论:DefNew+Tenured
优点:
简单而高效
对于限定单个CPU环境来说,没有线程交互的开销可以获得最高的单线程垃圾收集效率
缺点:
收集期间需要暂停所有应用线程,用户体验不好
应用场景:
Java虚拟机运行在Client模式下默认的新生代垃圾收集器
对应JVM参数是:-XX:+UseSerialGC
当我们使用此参数开启Serial,老年代默认会开启Serial Old,
即开启后会使用:Serial(Young区用)+Serial Old(Old区用)的收集器组合。
表示新生代和老年代都会使用串行回收收集器,
新生代使用复制算法,老年代使用标记压缩算法。
2.2老年代串行
老年代串行收集器使用的是标记压缩算法。由于老年代垃圾回收通常会使用比新生代回收更长的时间,因此,在堆空间较大的应用程序中,一旦老年代串行收集器启动,应用程序很可能会因此停顿较长的时间。虽然如此,老年代串行回收器可以和多种新生代回收器配合使用。
若要启用老年代串行回收器,可以尝试使用以下参数。
●-XX:+UseSerialGC: 新生代、老年代都使用串行回收器。
●-XX:+UseParNewGC: 新生代使用ParNew回收器,老年代使用串行收集器。
●-XX:+UseParallelGC: 新生代使用ParallelGC回收器,老年代使用并行收集器。
一次老年代串行回收器的工作输出日志类似如下信息:
java
25.299: [Full GC (Allocation Failure) 25.300: [Tenured: 126975K->84388K(126976K), 0.1865275 secs] 741375K->84388K(741376K), [Metaspace: 3476K->3476K(1056768K)], 0.1866490 secs] [Times: user=0.19 sys=0.00, real=0.18 secs]
2.3新生代并行回收器 ParNew
ParNew回收器是一个工作在新生代的垃圾收集器。它只是简单地将串行回收器多线程化 ,它的回收策略、算法以及参数和新生代串行回收器一样。 ParNew 回收器的工作示意图如图所示。ParNew回收器也是独占式 的回收器,在收集过程中,应用程序会全部暂停。但由于并行回收器使用多线程进行垃圾回收,因此,在并发能力比较强的CPU上,它产生的停顿时间要短于串行回收器,而在单CPU或者并发能力较弱的系统中,并行回收器的效果不会比串行回收器好,由于多线程的压力,它的实际表现很可能比串行回收器差。

开启ParNew回收器可以使用以下参数。
●-XX:+UseParNewGC:新生代使用ParNew回收器,老年代使用串行回收器。
●-XX:+UseConcMarkSweepGC: 新生代使用ParNew回收器,老年代使用CMS。
ParNew回收器工作时的线程数量可以使用-XX:ParallelGCThreads参数指定。一般,最好与CPU数量相当,避免过多的线程数,影响垃圾收集性能。在默认情况下,当CPU数量小于8个时,ParallelGCThreads 的值等于CPU数量,当CPU数量大于8个时,ParallelGCThreads 的值等于3+((5*CPU_ Count)/8)。
配置参数进行演示:-Xms10m -Xmx10m -XX:+PrintGCDetails -XX:+UseParNewGC

一次ParNew回收器的日志输出信息如下:
0.834: [GC 0.834: [ParNew: 13184K->1600K(14784K) ,0.0092203 secs] 13184K- >
1921K (63936K),0.0093401 secs] [Times: user=0.00 sys=0.00, real=0.00 secs ]
应用场景:
最常见的应用场景是配合老年代的CMS GC工作,其余的行为和Serial收集器完全一样
它是很多Java虚拟机运行在Server模式下新生代的默认垃圾收集器
对应JVM参数是:-XX:+UseParNewGC
启用ParNewGC收集器,只影响新生代的收集,不影响老年代
开启后会使用:ParNew(Young区用)+Serial Old(Old区用)的收集器组合
新生代使用复制算法,老年代使用标记压缩算法。
2.4ParallelGC新生代并行垃圾回收器

新生代ParallelGC回收器也是使用复制算法 的收集器。从表面上看,它和ParNew回收器一样,都是多线程、独占式的收集器。但是, ParallelGC回收器有个重要的特点:它非常关注系统的吞吐量。
新生代ParallelGC回收器可以使用以下参数启用。
●-XX:+UseParalleIGC: 新生代使用ParallelGC回收器,老年代使用ParallelOldGC。
●-XX:+UseParallelOldGC: 新生代使用ParallelGC 回收器,老年代使用ParallelOldGC
回收器。
对应JVM参数是: -XX:+UseParallelGC或-XX:+UseParallelOldGC(可互相激活,即配置其中一个,另一个会自动连带激活)
ParallelGC回收器提供了两个重要的参数用于控制系统的吞吐量。
Parallel重点关注的是:可控制的吞吐量
吞吐量=运行用户代码的时间/(运行用户代码时间+垃圾收集时间)
即比如程序运行100分钟,垃圾收集时间位1分钟,吞吐量为99%
高吞吐量意味着高效利用CPU的时间,它多用于在后台运算而不需要太多交互的任务
比如你在前台下个单,它停顿来算,交互性差,而对于科学计算,它自己在后台计算,停顿一会我们也不知道,但是它高效利用了CPU
●-XX:MaxGCPauseMillis: 设置最大垃圾收集停顿时间。它的值是一个大于0的整数。
ParallelGC在工作时,会调整Java堆大小或者其他一些参数,尽可能地把停顿时间控制在MaxGCPauseMillis以内。如果希望减少停顿时间,而把这个值设得很小,为了
达到预期的停顿时间,虚拟机可能会使用一个较小的堆(一个小堆比一个大堆回收快),
而这将导致垃圾回收变得很频繁,从而增加了垃圾回收总时间,降低了吞吐量。
●-XX:GCTimeRatio: 设置吞吐量大小。它的值是一个0到100之间的整数。假设
GCTimeRatio的值为n,那么系统将花费不超过1/(1+n)的时间用于垃圾收集。比如
GCTimeRatio等于19 (默认值),则系统用于垃圾收集的时间不超过1/(1+19)=5%。默
认情况下,它的取值是99,即不超过1/(1+99)=1%的时间用于垃圾收集。
除此以外,ParallelGC回收器与ParNew回收器另一个不同之处在于它还支持一种自适应的
GC调节策略。使用-XX:+UseAdaptiveSizePolicy可以打开自适应GC策略。在这种模式下,新生代的大小、eden和survivior的比例、晋升老年代的对象年龄等参数会被自动调整,以达到在堆大小、吞吐量和停顿时间之间的平衡点。在手工调优比较困难的场合,可以直接使用这种自适应的方式,仅指定虛拟机的最大堆、目标吞吐量( GCTimeRatio)和停顿时间(MaxGCPauseMillis),让虚拟机自己完成调优工作。
演示代码如上个例子:
-Xms10m -Xmx10m -XX:+PrintGCDetails -XX:+UseParallelGC

结论:PSYoungGen+ParOldGen
2.5ParallelOldGC老年代垃圾回收器
老年代ParallelOldGC 回收器也是一种多线程并发的收集器。和新生代ParallelGC回收器一样,它也是一种关注吞吐量的收集器。并且和ParallelGC新生代回收器搭配使用。
ParallelOldGC回收器使用标记压缩算法,图显示了老年代ParallelOldGC回收器的工作模式。

使用-XX:+UseParallelOldGC可以在新生代使用ParallelGC 回收器,老年代使用ParallelOldGC回收器。
这是一对非常关注吞吐量的垃圾回收器组合。在对吞吐量敏感的系统中,可以考虑使用。参数-XX:ParallelGCThreads也可以用于设置垃圾回收时的线程数量。
ParallelOldGC回收器的工作日志如下:
|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 1.500: [Full GC [PSYoungGen: 2682K->0K(19136K)] [ParOldGen: 28035K->30437K(43712K) ] 3071 7K-> 304 37K (62848K) [PSPermGen: 10943K-> 10928K (32768K) ],0.2902791secs] [Times: user=1.44 sys=0.03, real=0.30 secs] |
它显示了新生代、老年代以及永久区在回收前后的情况,以及FullGC所消耗的时间。
2.6CMS垃圾回收器
CMS收集器(Concurrent Mark Sweep:并发标记清除)是一种以获取最短回收停顿时间为目标的收集器 (低延迟)

与ParallelGC 和ParallelOldGC 不同,CMS回收器主要关注于系统停顿时间。
CMS工作时,主要步骤有:
初始标记、并发标记、预清理、重新标记、并发清除和并发重置。
其中初始标记 和重新标记 是独占系统资源的,而预清理、并发标记、并发清除和并发重置是可以和用户线程一起执行的。因此,从整体上说,CMS收集不是独占式的,它可以在应用程序运行过程中进行垃圾回收。

2.6.1 为什么CMS阶段划分会有STW独占阶段和并发执行阶段?
原理:
1、把对「标记准确性起决定性作用、无法并发处理」的步骤 设为 STW 独占阶段(初始标记、重新标记);
2、把可与用户线程并行、即使有临时误差也可后续修正 的步骤,设为 并发阶段(并发标记、预清理、并发清除、并发重置)。
原因:
1、初始标记(Initial Mark)------ STW 独占阶段
任务:标记 GC Roots 直接关联的老年代对象。
因为**GC Roots 的引用关系不能动态变化,**如果用户线程在这个阶段继续运行,可能会发生:
线程创建新对象并加入 GC Roots(比如新的局部变量);线程销毁 GC Roots 关联的对象(比如方法执行完毕,局部变量出栈)。
这些动态变化会导致根对象标记结果完全错误------ 后续所有标记步骤都会基于错误的根对象,最终必然误判存活 / 垃圾对象。
2、并发标记(Concurrent Mark)------ 并发执行阶段
任务:从初始标记的根关联对象 出发,遍历整个老年代的对象图,标记所有存活对象。
这个阶段的核心是批量遍历标记 ,而非最终准确标记。
01、遍历过程中,用户线程可以正常创建对象、修改引用关系。
02、即使出现「临时标记误差」(比如用户线程把一个可达对象变为不可达,或新增一个可达对象),也没关系 ------ 这些误差会在后续的 预清理、重新标记 阶段修正。
但是可能会产生浮动垃圾。
并发标记时,用户线程可能将一些原本可达的对象变为不可达(这些对象被称为 浮动垃圾)。
01、 浮动垃圾不会被本次 CMS 清理,只能等待下一次 GC 才能回收。
02、这也是 CMS 要求老年代预留一定内存空间的原因 ------ 不能等到内存满了再触发 GC,否则并发阶段用户线程的内存需求无法满足。
3、预清理(Preclean)------ 并发执行阶段
任务 :减少重新标记阶段的 STW 时间,做前置准备工作。
主要处理两个核心数据:
1、卡表脏数据:
新生代的对象可能引用老年代对象(跨代引用),ParNew 收集新生代时,会把「被新生代引用的老年代内存区域」标记为 脏卡。
预清理阶段会遍历这些脏卡,重新标记对应的老年代存活对象,避免这些工作堆积到重新标记阶段。
2、处理并发标记期间的引用变化:
CMS 通过 写屏障(Write Barrier) 记录并发标记时用户线程修改的引用关系,预清理阶段会提前处理这些记录。
并发执行原因:
预清理的本质是 「批量处理辅助数据」,不涉及「最终标记结果的确认」。
1、这个过程不会修改对象的核心引用关系,和用户线程的运行互不干扰。
2、即使处理过程中又产生新的脏卡,也可以继续处理 ------ 目标只是尽可能减少重新标记的工作量,而非追求绝对准确。
4. 重新标记(Remark)------ STW 独占阶段
任务:
修正并发标记、预清理阶段因用户线程并发运行导致的标记错误 ,生成 100% 准确的存活对象标记结果。
1、处理剩余的卡表脏数据。
2、处理写屏障记录的所有引用变化。
3、扫描 JVM 的 元数据区(比如类的静态变量),确保没有遗漏。
STW独占原因:
核心原因:最终标记结果必须绝对准确,否则会误删存活对象。
如果这个阶段不停止用户线程,会出现两个致命问题:
1、引用关系持续变化:用户线程每修改一次引用,标记结果就会失效一次,修正工作永远无法完成。
2、误删风险 :若遗漏了一个存活对象的标记,CMS 会把它当成垃圾清理,导致应用抛出 NullPointerException 甚至崩溃。
5、并发清除(Concurrent Sweep)------ 并发执行阶段
任务:
遍历老年代内存,清理所有未被标记的垃圾对象,释放内存空间。
并发执行原因:
这个阶段的核心是 「根据已确认的标记结果做清理」------ 存活对象的标记已经 100% 准确,清理过程只需要:
1、识别未标记的垃圾对象。
2、将垃圾对象的内存地址加入空闲列表,供后续对象分配使用。
清理过程不修改任何对象的引用关系 ,用户线程只需要访问存活对象,因此两者可以完全并行执行。
问题是可能会产生内存碎片 。标记 - 清除算法的固有缺点 ------ 清理垃圾后,内存空间会变成不连续的碎片。
**6、**并发重置(Concurrent Reset)------ 并发执行阶段
任务:
重置 CMS 收集器的内部数据结构,为下一次 GC 做准备。
并发执行原因:
这个阶段是纯「工具性数据结构的重置」,不涉及任何对象的标记或清理,和用户线程的运行完全无冲突,因此可以并发执行。
| 阶段 | 类型 | 核心价值 | 为何STW/并发 |
|---|---|---|---|
| 初始标记 | STW | 确定准确的 GC Roots 直接关联对象 | 根对象引用不能动态变化,否则后续标记全错 |
| 并发标记 | 并发 | 批量标记大部分存活对象,减少 STW 时间 | 允许临时误差,后续可修正 |
| 预清理 | 并发 | 前置处理脏数据,缩短重新标记的 STW 时间 | 辅助性工作,不影响核心标记准确性 |
| 重新标记 | STW | 修正所有误差,生成 100% 准确的标记结果 | 必须停止用户线程,否则无法完成准确修正 |
| 并发清除 | 并发 | 清理垃圾,释放内存 | 标记结果已确定,清理不影响用户线程 |
| 并发重置 | 并发 | 为下一次 GC 做准备 | 纯数据结构重置,无冲突 |
- STW 阶段是「准确性的必要代价」:初始标记和重新标记的 STW,是为了保证垃圾标记的绝对准确,避免误删存活对象;
- 并发阶段是「低延迟的核心手段」:把最耗时的遍历、清理工作放到并发阶段,最大化减少应用停顿;
- 阶段划分是「平衡术」 :CMS 通过「短 STW + 长并发」的组合,完美契合了低延迟应用场景(如 Web 服务、金融交易系统)的需求。
2.6.2 CMS参数设置
-XX:+UseConcMarkSweepGC CMS 启用CMS回收器,是多线程回收器,设置合理的工作线程数量也对系统性能有重要的影响。
CMS默认启动的并发线程数是(ParallelGCThreads+3)/4)。
并发线程数量也可以通过-XX:ConcGCThreads或者-XX:ParallelCMSThreads参数手工设定。
当CPU资源比较紧张时,受到CMS回收器线程的影响,应用系统的性能在垃圾回收阶段可能会非常糟糕。
在JVM中的并发和并行分别指什么:
并发是指收集器和应用线程交替执行。
并行是指应用程序停止,同时由多个线程一起执行GC。因此并行回收器不是并发的。因为并行回收器执行时,应用程序完全挂起,不存在交替执行的步骤。
在CMS回收过程中,应用程序仍然在不停地工作,又会不断地产生垃圾。这些新生成的垃圾在当前CMS回收过程中是无法清除的。同时,因为应用程序没有中断,所以在CMS回收过程中,还应该确保应用程序有足够的内存可用。因此,CMS回收器不会等待堆内存饱和时才进行垃圾回收,而是当堆内存使用率达到某一阈值时便开始进行回收,以确保应用程序在CMS工作过程中,依然有足够的空间支持应用程序运行。
这个回收阈值可以使用-XX:CMSInitiatingOccupancyFraction来指定,默认是68。即当老年代的空间使用率达到68%时,会执行一次CMS回收。如果应用程序的内存使用率增长很快,在CMS的执行过程中,已经出现了内存不足的情况,此时,CMS回收就会失败,虚拟机将启动老年代串行收集器进行垃圾回收。如果这样,应用程序将完全中断,直到垃圾回收完成,这时,应用程序的停顿时间可能会较长。
我们可以根据应用程序的特点,对-XX:CMSInitiatingOccupancyFraction进行调优。
如果内存增长缓慢 ,则可以设置个稍大 的值,大的阈值可以有效降低 CMS的触发频率,减少老年代回收的次数可以较为明显地改善应用程序性能。
反之,如果应用程序内存使用率增长很快 ,则应该降低阈值,以避免频繁触发老年代串行收集器。
CMS是一个基于标记清除算法的回收器。在本章之前的篇幅中已经提到,标记清除算法将会造成大量内存碎片,离散的可用空间无法分配较大的对象。图显示了CMS回收前后老年代的情况。

在这种情况下,即使堆内存仍然有较大的剩余空间,也可能会被迫进行一次垃圾回收,以换取一块可用的连续内存。这种现象对系统性能是相当不利的,为了解决这个问题,CMS回收器还提供了几个用于内存压缩整理的参数。
-XX:+UseCMSCompactAtFullCollection开关可以使CMS在垃圾收集完成后,进行一次内存碎片整理,内存碎片的整理不是并发进行的。
-XX:CMSFullGCsBeforeCompaction参数可以用于设定进行多少次CMS回收后,进行一次内存压缩。
2.6.3 CMS日志
执行上一节代码,参数:
-Xms10m -Xmx10m -XX:+PrintGCDetails -XX:+UseConcMarkSweepGC

以上信息是一次CMS收集的输出。可以看到,在CMS回收器的工作过程中,包括了初始化标记、并发标记、预清理、重新标记、并发清理和重发重置等几个重要阶段。在日志中,还可以看到CMS的耗时以及堆内存信息。
2.7、G1回收器
2.7.1定义与特点
定义:
G1回收器(Garbage-First) 是在JDK 1.7 中正式使用的全新的垃圾回收器,从长期目标来看,它是为了取代CMS回收器。从分代上看,G1依然属于分代垃圾回收器,它会区分年轻代和老年代,依然有eden区和survivor区,但从堆的结构上看,它并不要求整个eden区、年轻代或者老年代都连续。它使用了分区算法。
特点:
●并行性: G1在回收期间,可以由多个GC线程同时工作,有效利用多核计算能力尽量缩短STW。
●并发性: G1拥有与应用程序交替执行的能力,部分工作可以和应用程序同时执行,因
此一般来说,不会在整个回收期间完全阻塞应用程序。
●分代GC:G1依然是一个分代收集器,但是和之前回收器不同,它同时兼顾年轻代和
老年代。对比其他回收器,它们或者工作在年轻代,或者工作在老年代,宏观上看G1之中不再区分年轻代和老年代,把内存划分成多个独立的子区域(Region)。
●空间整理: G1在回收过程中,会进行适当的对象移动,不像CMS,只是简单地标记清
理对象,在若干次GC后,CMS必须进行一次碎片整理。而G1不同,它每次回收都会
有效地复制对象,减少空间碎片。
●可预见性: 由于分区的原因,G1可以只选取部分区域进行内存回收,这样缩小了回收的范围,因此对于全局停顿也能得到较好的控制。
1、新生代GC
01、Region区域化垃圾收集器:
最大的好处是化整为零,避免全内存扫描,只需要按照区域来进行扫描即可区域化内存划片Region,整体变为了一些不连续的内存区域,避免了全内存区的GC操作。
02、核心思想:
- 将整个堆内存区域分成大小相同的子区域(Region),在JVM启动时会自动设置这些子区域的大小
- 在堆的使用上,G1并不要求对象的存储一定是物理上连续的只要逻辑上连续即可,每个分区也不会固定地为某个代服务,可以按需在年轻代和老年代切换
- 启动时可以通过参数-XX:G1HeapRegionSize=n可指定分区大小(1MB~32MB,且必须是2的幂),默认将整堆划分为2048个分区
- 大小范围在1MB~32MB,最多能设置2048个区域,也即能够支持的最大内存为:32MB*2048=65536MB=64G内存

G1算法将堆划分为若干个区域(Region),它仍然属于分代收集器。
01、这些Region的一部分包含新生代
新生代的垃圾收集依然采用暂停所有应用线程的方式,将存在对象拷贝到老年代或Survivor空间。
02、这些Region的一部分包含老年代
G1收集器通过将对象从一个区域复制到另外一个区域,完成了清理工作。这意味着,在正常的处理过程中,G1完成了堆的压缩(至少是部分堆的压缩),这样也就不会有CMS内存问题的存在。
03、在G1中,还有一种特殊的区域,叫Humongous区域
1.如果一个对象占用的空间超过了分区容量50%以上,G1收集器就认为这是一个巨型对象。这种巨型对象默认直接会被分配在老年代。
2.但是如果它是一个短期存在的巨型对象,就会对垃圾收集器造成负面影响,为了解决这个问题,G1划分了一个Humongous区,它用来专门存放巨型对象。
3.如果H区装不下一个巨型对象,那么G1会寻找连续的H分区来存储,为了能找到连续的H区,有时候不得不启动FullGC.
回收步骤:
G1收集器下的YoungGC
1.针对Eden区进行收集,Eden区耗尽后会被触发,主要是小区域收集+形成连续的内存块,避免内存碎片。
2.Eden区的数据移动到Survivor区,假如出现Survivor区空间不够,Eden区数据会部分晋升到Old区。
3.Survivor区的数据移动到新的Survivor区,部分数据晋升到Old区。
4.最后Eden区收拾干净了,GC结束,用户的应用程序继续执行。

2、回收过程
G1的并发阶段和CMS有点类似,它们都是为了降低一次停顿时间,而将可以和应用程序并发的部分单独提取出来执行。
并发标记周期可以分为以下几步:

●初始标记:
标记从根节点直接可达的对象。这个阶段会伴随一次新生代 GC ,它是会产生全局停顿的,应用程序线程在这个阶段必须停止执行。
●根区域扫描:
在这个阶段,将扫描由survivor区直接可达的老年代区域,并标记这些直接可达的对象。这个过程是可以和应用程序并发执行的。但是根区域扫描不能和新生代GC同时执行(因为根区域扫描依赖survivor区的对象,而新生代GC会修改这个区域)。
●并发标记:
和CMS类似,并发标记将会扫描并查找整个堆的存活对象,并做好标记。这是一个并发的过程,并且这个过程可以被一次新生代GC打断。
●重新标记:
和CMS一样,重新标记也是会产生应用程序停顿的。由于在并发标记过程中,应用程序依然在运行,因此标记结果可能需要进行修正,所以在此对上一次的标记结果进行补充。
●独占清理:
这个阶段是会引起停顿的。它将计算各个区域的存活对象和GC回收比例并进行排序,识别可供混合回收的区域。在这个阶段,还会更新记忆集(Remebered Set)。
该阶段给出了需要被混合回收的区域并进行了标记,在混合回收阶段,需要这些信息。
●并发清理阶段:
这里会识别并清理完全空闲的区域。它是并发的清理,不会引起停顿。
3、必要时FullGC
和CMS类似,并发收集由于让应用程序和GC线程交替工作,因此总是不能完全避免在特别繁忙的场合会出现在回收过程中内存不充足的情况。当遇到这种情况时,G1也会转入一个Full GC进行回收。
2.7.2 G1的参数设置
1、-XX:+UseG1GC可以使用标记打开G1收集器开关。
2、-XX:MaxGCPauseMillis它用于指定目标最大停顿时间。
如果任何一次停顿超过这个设置值时,G1就会尝试调整新生代和老年代的比例、调整堆大小、调整晋升年龄等手段,试图达到预设目标。
对于性能调优来说,有时候,总是鱼和熊掌不可兼得的,如果停顿时间缩短,对于新生代来说,这意味着很可能要增加新生代GC的次数,GC反而会变得更加频繁。对于老年代区域来说,为了获得更短的停顿时间,那么在混合GC收集时,一次收集的区域数量也会变少,这样无疑增加了进行Full GC的可能性。
3、-XX:ParallelGCThreads,它用于设置并行回收时,GC的工作线程数量。
4、-XX:InitiatingHeapOccupancyPercent参数可以指定当整个堆使用率达到多少时,触发
并发标记周期的执行。
默认是45,即当整个堆占用率达到45%时,执行并发标记周期。InitiatingHeapOccupancyPercent 一旦设置,始终都不会被G1收集器修改,这意味着G1收集器不会试图改变这个值,来满足MaxGCPauseMillis的目标。如果InitiatingHeapOccupancyPercent值设置偏大,会导致并发周期迟迟不能启动,那么引起Full GC的可能性也大大增加,反之,一个过小的InitiatingHeapOccupancyPercent 值,会使得并发周期非常频繁,大量GC线程抢占CPU,会导致应用程序的性能有所下降。