1、串行回收器(Serial)
串行回收器(Serial Garbage Collector) 是JVM中最古老、最简单的垃圾收集器,采用单线程 进行垃圾回收,执行期间会暂停所有应用线程(Stop-The-World)。
串行回收器主要有两个特点:
- 它仅仅使用单线程进行垃圾回收。
- 它是独占式的垃圾回收。
在串行收集器进行垃圾回收时,Java应用程序中的线程都需要暂停,等待垃圾回收的完成。如图所示,在串行回收器运行时,应用程序中的所有线程都停止工作,进行等待。这种现象称之为"Stop-The-World"。它将造成非常糟糕的用户体验,在实时性要求较高的应用场景中,这种现象往往是不能被接受的。

1、新生代串行
新生代串行处理器使用复制算法,实现相对简单、逻辑处理特别高效、且没有线程切换的开销。在诸如单CPU处理器等硬件平台不是特别优越的场合,它的性能表现可以超过并行回收器和并发回收器。
使用-XX:+UseSerialGC参数可以指定使用新生代串行收集器和老年代串行收集器。
当虚拟机在Client模式下运行时,它是默认的垃圾收集器。
plain
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();
}
}
}

- 1."GC"中接下来的"[DefNew"表示GC发生的区域,这里显示的区域名称与使用的GC收集器是密切相关的,例如上面样例所使用的Serial收集器中的新生代名为"Default New Generation",所以显示的是"[DefNew"。如果是ParNew收集器,新生代名称就会变为"[ParNew",意为"Parallel New Generation"。如果采用Parallel Scavenge收集器,那它配套的新生代称为"PSYoungGen",老年代和永久代同理,名称也是由收集器决定的。
- 2、后面方括号内部的"310K->194K(2368K)"、"2242K->0K(2368K)",指的是该区域已使用的容量->GC后该内存区域已使用的容量(该内存区总容量)。方括号外面的"310K->194K(7680K)"、"2242K->2241K(7680K)"则指的是GC前Java堆已使用的容量->GC后Java堆已使用的容量(Java堆总容量)。
- 3、再往后"0.0269163 secs"表示该内存区域GC所占用的时间,单位是秒。最后的"[Times: user=0.00 sys=0.00 real=0.03 secs]"
- (1)user -- 此次垃圾回收, 垃圾收集线程消耗的所有CPU时间(Total CPU time)
- (2)sys -- 操作系统调用(OS call) 以及等待系统事件的时间(waiting for system event)
- (3)real -- 应用程序暂停的时间(Clock time). 由于串行垃圾收集器(Serial Garbage Collector)只会使用单个线程, 所以 real time 等于 user 以及 system time 的总和.
优点:
- 简单而且高效
- 对于限定单个CPU环境,没有线程交互的开销可以获取最高的单线程垃圾回收效率
缺点:
- 收集期间需要暂停所有应用线程,用户体验不好
2、老年代串行
老年代串行收集器使用的是标记压缩算法。由于老年代垃圾回收通常会使用比新生代回收更长的时间,因此,在堆空间较大的应用程序中,一旦老年代串行收集器启动,应用程序很可能会因此停顿较长的时间。虽然如此,老年代串行回收器可以和多种新生代回收器配合使用,若要启用老年代串行回收器,可以尝试使用以下参数。
- -XX:+UseSerialGC: 新生代、老年代都使用串行回收器。
- -XX:+UseParNewGC: 新生代使用ParNew回收器,老年代使用串行收集器。
- -XX:+UseParallelGC: 新生代使用ParallelGC回收器,老年代使用Parallel Old收集器。
一次老年代串行回收器的工作输出日志类似如下信息:

2、 新生代并行回收器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)。
plain
-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区用)的收集器组合
新生代使用复制算法,老年代使用标记压缩算法
3、ParallelGC新生代并行垃圾回收器

新生代ParallelGC回收器也是使用复制算法的收集器。从表面上看,它和ParNew回收器一样,都是多线程、独占式的收集器。但是, ParallelGC回收器有个重要的特点:它非常关注系统的吞吐量。
新生代ParallelGC回收器可以使用以下参数启用。
-XX:+UseParalleIGC: 新生代使用ParallelGC回收器,老年代使用ParallelOldGC。
-XX:+UseParallelOldGC: 新生代使用ParallelGC 回收器,老年代使用ParallelOldGC回收器。
对应JVM参数是:****-XX:+UseParallelGC或-XX:+UseParallelOldGC(可互相激活,即配置其中一个,另一个会自动连带激活)
ParallelGC回收器提供了两个重要的参数用于控制系统的吞吐量。
Parallel重点关注的是:可控制的吞吐量
吞吐量=运行用户代码的时间/(运行用户代码时间+垃圾收集时间)
即比如程序运行100分钟,垃圾收集时间位1分钟,吞吐量为99%
高吞吐量意味着高效利用CPU的时间,它多用于在后台运算而不需要太多交互的任务
比如你在前台下个单,它停顿来算,交互性差,而对于科学计算,它自己在后台计算,停顿一会我们也不知道,但是它高效利用了CPU。
- -X: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),让虚拟机自己完成调优工作。

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

使用-XX:+UseParallelOldGC可以在新生代使用ParallelGC 回收器,老年代使用ParallelOldGC回收器。
这是一对非常关注吞吐量的垃圾回收器组合。在对吞吐量敏感的系统中,可以考虑使用。参数-XX:ParallelGCThreads也可以用于设置垃圾回收时的线程数量。

5、CMS垃圾回收器(并发标记清除)
· 是一种以获取最短回收停顿时间为目标的收集器

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

1、CMS参数设置
-XX:+UseConcMarkSweepGC 启用CMS回收器,是多线程回收器,设置合理的工作线程数量也对系统性能有重要的影响。CMS默认启动的并发线程数是(ParallelGCThreads+3)/4)。
并发线程数量也可以通过-XX:ConcGCThreads或者-XX:ParallelCMSThreads参数手工设定。
当CPU资源比较紧张时,受到CMS回收器线程的影响,应用系统的性能在垃圾回收阶段可能会非常糟糕。
- 注意:并发是指收集器和应用线程交替执行,并行是指应用程序停止,同时由多个线程一起执行GC。因此并行回收器不是并发的。因为并行回收器执行时,应用程序完全挂起,不存在交替执行的步骤。
在CMS回收过程中,应用程序仍然在不停地工作,又会不断地产生垃圾。这些新生成的垃圾在当前CMS回收过程中是无法清除的。同时,因为应用程序没有中断,所以在CMS回收过程中,还应该确保应用程序有足够的内存可用。因此,CMS回收器不会等待堆内存饱和时才进行垃圾回收,而是当堆内存使用率达到某一阈值时便开始进行回收,以确保应用程序在CMS工作过程中,依然有足够的空间支持应用程序运行。
这个回收阈值可以使用-XX:CMSInitiatingOccupancyFraction来指定,默认是68。即当老年代的空间使用率达到68%时,会执行一次CMS回收。如果应用程序的内存使用率增长很快,在CMS的执行过程中,已经出现了内存不足的情况,此时,CMS回收就会失败,虚拟机将启动老年代串行收集器进行垃圾回收。如果这样,应用程序将完全中断,直到垃圾回收完成,这时,应用程序的停顿时间可能会较长。
注意:通过-XX:CMSInitiatingOccupancyFraction 可以指定当老年代空间使用率达到多少时,进行一次CMS垃圾回收。
因此,根据应用程序的特点,可以对-XX:CMSInitiatingOccupancyFraction进行调优。如果内存增长缓慢,则可以设置个稍大的值,大的阈值可以有效降低CMS的触发频率,减少老年代回收的次数可以较为明显地改善应用程序性能。反之,如果应用程序内存使用率增长很快,则应该降低这个阈值,以避免频繁触发老年代串行收集器。
CMS是一个基于标记清除算法的回收器。标记清除算法将会造成大量内存碎片,离散的可用空间无法分配较大的对象。图显示了CMS回收前后老年代的情况。

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


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

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

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

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