jvm-垃圾回收(垃圾收集器)

垃圾收集器分类

垃圾收集器没有在规范中进⾏过多的规定,可以由不同的⼚商、不同版本的JVM来实现。由于JDK版本的处于⾼速迭代过程中,因此java发展⾄今已经衍⽣了众多GC版本。从不同⻆度分析垃圾收集器,可以将GC分为不同的类型。
1: 按线程数分,可以分为串⾏垃圾回收器和并⾏垃圾回收器。

  • 串⾏回收指的是在同⼀时间段只允许有⼀个CPU⽤于执⾏垃圾回收操作,此时⼯作线程被暂停,直
    ⾄垃圾收集⼯作结束。
  • 和串⾏回收相反,并⾏收集可以运⽤多个CPU同时执⾏垃圾回收,因此提升了应⽤的吞吐量,不过
    并⾏回收仍然与串⾏回收⼀样,采⽤独占式,使⽤了“Stop-the-world”机制。
  • 在诸如单CPU处理器或者较⼩内存等硬件场合中,串⾏回收器的性能表现可以超过并⾏回收器和并
    发回收器。所以,串⾏回收默认被应⽤在客户端Client模式下的JVM中。
  • 在并发能⼒较强的CPU上,并⾏回收器产⽣的停顿时间要短于串⾏回收器。

2:按照⼯作模式分,可以分为并发式垃圾回收器和独占式垃圾回收器。

  • 并发式垃圾回收器与应⽤程序线程交替⼯作,以尽可能减少应⽤程序的停顿时间。
  • 独占式垃圾回收器⼀旦运⾏,就停⽌应⽤程序中的所有⽤户线程,直到垃圾回收过程完全结束。

3:按碎⽚处理⽅式分,可分为压缩式垃圾回收器和⾮压缩式垃圾回收器。

  • 压缩式垃圾回收器在回收完成后,对存活对象进⾏压缩整理,消除回收后的碎⽚。
  • ⾮压缩式的垃圾回收器不进⾏这不操作,会产⽣碎⽚。

4:按⼯作的内存空间分,⼜可分为年轻代垃圾回收器和⽼年代垃圾回收器

如何评估GC的性能指标

  • 吞吐量:运⾏⽤户代码的时间占总运⾏时间的⽐例 (总运⾏时间=程序的运⾏时间+内存回收的时
    间);
  • 暂停时间:执⾏垃圾收集时,程序的⼯作线程被暂停的时间;
  • 内存占⽤:java堆区所占的内存⼤⼩;

吞吐量就是CPU⽤于运⾏⽤户代码的时间与CPU总消耗的时间的⽐值,即吞吐量=运⾏⽤户代码时间/(运⾏⽤户代码时间+垃圾收集时间)。⽐如:虚拟机总共运⾏了100分钟,其中垃圾收集花掉1分钟,那么吞吐量就是99%。这种情况下,应⽤程序能容忍较⾼的暂停时间,因此,⾼吞吐量的应⽤程序有更⻓的时间基准,快速响应是不必考虑的。

暂停时间是指⼀个时间段内应⽤程序线程暂停,让GC线程执⾏的状态。⽐如:GC期间100毫秒的暂停时间意味这在这100毫秒期间内没有应⽤程序线程是活动的。

注重吞吐量:吞吐量优先,意味着在单位时间内,STW的时间最短:0.2 + 0.2= 0.4 s
在这里插入图片描述
注重低延迟:暂停时间优先,意味这尽可能让单次STW的时间最短:0.1 + 0.1 + 0.1 + 0.1 + 0.1 =0.5 s
在这里插入图片描述
这三者共同构成⼀个”不可能三⻆“。三者总体的表现会随着技术进步⽽越来越好。⼀款优秀的收集器通常最多同时满⾜其中的两项。

简单来说,主要抓住两点:

  • 吞吐量
  • 暂停时间
    在设计(或使⽤)GC算法时,必须确定我们的⽬标:⼀个GC算法只可能针对两个⽬标之⼀(即只专注于较⼤吞吐量或最⼩暂停时间),或尝试找⼀个⼆者的折衷。现在标准,在最⼤吞吐量优先的情况下,降低停顿时间

垃圾收集器发展史

有了虚拟机,就⼀定有需要收集垃圾的机制,这就是Garbage Collection,对应的产品我们称之为Garbage Collector

  • 1999年随着JDK1.3.1 ⼀起来的是串⾏⽅式的Serial GC,它是第⼀款GC。ParNew垃圾收集器是Serial收集器的多线程版本。
  • 2002年2⽉26⽇,Parallel GC和Concurrent Mark Sweep GC跟随着JDK1.4.2 ⼀起发布
  • Parallel GC在JDK6之后成为HotSpot默认GC。
  • 2012年,在JDK1.7u4版本中,G1可⽤。
  • 2017年,JDK9中G1变成默认的垃圾收集器,以替代CMS。
  • 2018年3⽉,JDK10中G1垃圾回收器的并⾏完整垃圾回收,实现并⾏性来改善最坏情况下的延迟。
  • 2018年9⽉,JDK11发布。引⼊Epsilon垃圾回收器,⼜被称为"No-Op(⽆操作)"回收器。同时引ZGC(Oracle 发布):可伸缩的低延迟垃圾回收器(Experimental)
  • 2019年3⽉,JDK12发布。增强G1,⾃动返回未⽤堆内存给操作系统。同时,引⼊Shenandoah
    GC(Red Hat 开发):低停顿时间的GC(Experimetal)
  • 2019年9⽉,JDK13发布。增强ZGC,⾃动返回未⽤堆内存给操作系统。
  • 2020年3⽉,JDK14发布。删除CMS垃圾回收器。扩展ZGC在macOS和Windows上的应⽤

经典垃圾回收器

  • 串⾏回收器:Serial 、 Serial Old
  • 并⾏回收器:ParNew、Parallel Scavenge、Parallel Old
  • 并发回收器:CMS、G1

垃圾回收器的组合关系

如果说垃圾收集算法是内存回收的⽅法论,那么垃圾收集器就是内存回收的具体实现。下图展示了7种作⽤于不同分代的收集器,其中⽤于回收新⽣代的收集器包括Serial、PraNew、Parallel Scavenge,回收⽼年代的收集器包括Serial Old、Parallel Old、CMS,还有⽤于回收整个Java堆的G1收集器。不同收集器之间的连线表示它们可以搭配使⽤。
在这里插入图片描述
为什么要有很多收集器,⼀个不够吗 ?因为java的使⽤场景很多,移动端、服务器等。所以就需要针对不同的场景,提供不同的垃圾收集器,提⾼垃圾收集的性能。

虽然我们会对各个收集器进⾏⽐较,但并⾮为了挑选⼀个最好的收集器出来。没有⼀种放之四海⽽皆准、任何场景下都适⽤的完美收集器存在,更加没有万能的收集器。所以 我们选择的只是对具体应⽤最合适的收集器。

如何查看默认的垃圾回收器

  • -XX:+PrintCommandLineFlags:查看命令⾏相关参数(包含使⽤的垃圾收集器)
  • 使⽤命令⾏指令: jinfo -flag 相关垃圾回收参数 进程ID
import java.util.ArrayList;
import java.util.List;
/**
* -XX:+PrintCommandLineFlags -XX:+UseConcMarkSweepGC
*/
public class GCUseTest {
	public static void main(String[] args) {
		List<byte[]> list = new ArrayList<>();
		while (true){
			byte[] arr = new byte[100];
			list.add(arr);
			try {
				Thread.sleep(10);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
}

Serial(复制算法)

Serial/Serial Old收集器是最基本最古⽼的收集器,Serial是JDK1.3之前回收新⽣代唯⼀的选择。它是⼀个单线程收集器,并且在它进⾏垃圾收集时,必须暂停所有⽤户线程。

Serial收集器是作为HotSpot中Client模式下的默认新⽣代垃圾收集器,采⽤的是复制算法。Serial Old收集器是针对⽼年代的收集器,采⽤的是标记-整理算法。

如下是 Serial 收集器和 Serial Old 收集器结合进⾏垃圾收集的示意图,当⽤户线程都执⾏到安全点时,所有线程暂停执⾏,Serial 收集器以单线程,采⽤复制算法进⾏垃圾收集⼯作,收集完之后,⽤户线程继续开始执⾏。它的”单线程“的意义并不仅仅说明它只会使⽤⼀个CPU或⼀条收集线程去完成垃圾收集⼯作,更᯿要的是在它进⾏垃圾收集时,必须暂停其他所有的⼯作线程,直到它收集结束(Stop the world)
在这里插入图片描述

  • 优点
    实现简单⾼效(与其他收集器的单线程相⽐),对于限定单个CPU的环境来说,Serial收集器由于没有线程交互的开销,专⼼做垃圾收集⾃然可以获得最⾼的单线程收集效率。
  • 缺点
    会给⽤户带来停顿。
  • 适⽤场景
    单核服务器。
    可以⽤ -XX:+UseSerialGC 参数可以指定年轻代和⽼年代都使⽤串⾏收集器。等价于 新⽣代⽤Serial GC,并且⽼年代⽤ Serial Old GC
import java.util.ArrayList;
import java.util.List;
/**
* -XX:+UseSerialGC -XX:+PrintCommandLineFlags
*/
public class GCUserTest {
	public static void main(String[] args){
		List<String> list = new ArrayList<>();
		String str = "test.com";
		while(true){
			list.add(str);
			str += str;
			try {
				Thread.sleep(10);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
}
  • 总结
    这种垃圾收集器了解即可,现在已经不⽤串⾏的了。⽽且在限定单核CPU才可以使⽤,现在都不是单核的了。
    对于交互较强的应⽤⽽⾔,这种垃圾收集器是不能接受的。⼀般在java web应⽤程序中是不会使⽤串⾏垃圾收集器的。

Serial Old(标记-整理算法)

⽼年代单线程收集器,Serial收集器的⽼年代版本;Serial Old是运⾏在Client模式下默认的⽼年代的垃圾回收器。

Serial Old在server模式下主要有两个⽤途:

  • 1:与新⽣代的Parallel Scavenge配合使⽤
  • 2:作为⽼年代CMS收集器的后备垃圾收集⽅法。

适⽤场景:

  • Client 模式(桌⾯应⽤);
  • 单核服务器;
  • 与 Parallel Scavenge 收集器搭配;
  • 作为 CMS收集器的后备预案。

ParNew (复制算法)

如果说Serial GC是年轻代中的单线程垃圾收集器,那么ParNew收集器就是Serial收集器的多线程版本。

新⽣代收并⾏集器,ParNew收集器是Serial收集器的多线程版本,使⽤多个线程进⾏垃圾收集。在多核CPU环境下有着⽐Serial更好的表现;ParNew收集器在年轻代中同样也是采⽤复制算法、“stop-the-wold”机制

如下是 ParNew 收集器和 Serial Old 收集器结合进⾏垃圾收集的示意图,当⽤户线程都执⾏到安全点时,所有线程暂停执⾏,ParNew 收集器以多线程,采⽤复制算法进⾏垃圾收集⼯作,收集完之后,⽤户线程继续开始执⾏。
在这里插入图片描述
对于新⽣代,回收次数频繁,使⽤并⾏⽅式⾼效。

对于⽼年代,回收次数少,使⽤串⾏⽅式节省资源。(CPU并⾏需要切换线程,串⾏可以省去切换线程的资源)。

由于ParNew收集器是基于并⾏回收,那么是否可以断定ParNew收集器的回收效率在任何场景下都会⽐Serial收集器更⾼效?(扩展点:多线程程序效率⼀定⾼于单线程程序吗?)

ParNew收集器运⾏在多CPU的环境下,由于可以充分利⽤多CPU、多核⼼等物理硬件资源优势,可以更快速地完成垃圾收集,提升程序的吞吐量。

但是在单个CPU的环境下,ParNew收集器不⽐Serial收集器更⾼效。虽然Serial收集器是基于串⾏回收,但是由于CPU不需要频繁地做任务切换,因此可以有效避免多线程交互过程中产⽣的⼀些额外开销。

适⽤场景

  • 多核服务器;
  • 与 CMS 收集器搭配使⽤(除Serial外,⽬前只有ParNew GC能与CMS收集器配合⼯作)。

参数

  • 当使⽤ -XX:+UseConcMarkSweepGC 来选择 CMS 作为⽼年代收集器时,新⽣代收集器默认就是 ParNew,也可以⽤ -XX:+UseParNewGC 来指定使⽤ ParNew 作为新⽣代收集器。
  • -XX:ParallelGCThreads 限制线程数ᰁ,默认开启和cpu数据相同的线程数。
import java.util.ArrayList;
import java.util.List;
/**
* -XX:+PrintCommandLineFlags -XX:+UseConcMarkSweepGC
*/
public class GCUseTest {
	public static void main(String[] args) {
		List<byte[]> list = new ArrayList<>();
		while (true){
			byte[] arr = new byte[100];
			list.add(arr);
			try {
				Thread.sleep(10);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}

Parallel Scavenge(复制算法)

HotSpot的年轻代中除了拥有ParNew收集器是基于并⾏回收的以外,Parallel Scavenge收集器同样也采⽤了复制算法、并⾏回收和“stop the world”机制。
那么Parallel收集器的出现是否是多此⼀举?

  • 和ParNew收集器不同,Parallel Scavenge收集器的⽬标是达到⼀个可控制的吞吐量(Throughput),它也被称为吞吐量优先的垃圾收集器。
  • ⾃适应调节策略也是Parallel Scavenge与ParNew⼀个重要区别。

⾼吞吐量意味着⾼效利⽤ CPU。⾼吞吐量可以⾼效率的利⽤CPU时间,尽快完成程序的运算任务。

如下是 Parallel 收集器和 Parallel Old 收集器结合进⾏垃圾收集的示意图,在新⽣代,当⽤户线程都执⾏到安全点时,所有线程暂停执⾏,ParNew 收集器以多线程,采⽤复制算法进⾏垃圾收集⼯作,收集完之后,⽤户线程继续开始执⾏;在⽼年代,当⽤户线程都执⾏到安全点时,所有线程暂停执⾏,Parallel Old 收集器以多线程,采⽤标记-整理算法进⾏垃圾收集⼯作。
在这里插入图片描述
适⽤场景

  • 注重吞吐量,⾼效利⽤ CPU,需要⾼效运算且不需要太多交互。适合后台应⽤等对交互相应要求不
    ⾼的场景;例如,那些执⾏批量处理、订单处理、⼯资⽀付、科学计算的应⽤程序。

参数配置:

  • -XX:+UseParallelGC 来选择 Parallel Scavenge 作为新⽣代收集器,
  • 分别适⽤于新⽣代和⽼年代。默认jdk8是开启的
  • 上⾯两个参数,默认开启⼀个,另⼀个也会被开启。(互相激活)
import java.util.ArrayList;
import java.util.List;
/**
* -XX:+PrintCommandLineFlags
*/
public class GCUseTest {
	public static void main(String[] args) {
		List<byte[]> list = new ArrayList<>();
		while (true){
			byte[] arr = new byte[100];
			list.add(arr);
			try {
				Thread.sleep(10);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
}
~ jinfo -flag UseParallelGC 21999
-XX:+UseParallelGC
➜ ~ jinfo -flag UseParallelOldGC 22037
-XX:+UseParallelOldGC

-XX:ParallelGCThreads设置年轻代并⾏收集器的线程数。⼀般地,最好与CPU数ᰁ相等。以避免过多的线程数影响垃圾收集性能。

  • 默认情况下,当CPU数量⼩于8个,ParallelGCThreads的值等于CPU数量。
  • 当CPU数ᰁ⼤于8个,ParallelGCThreads的值等于3+[5*CPU-Count]/8]。
-> jinfo -flag ParallelGCThreads 19231
-XX:ParallelGCThreads=8

-XX:MaxGCPauseMillis 设置垃圾收集器最⼤停顿时间(即STW的时间)。单位是毫秒。

  • 为了尽可能地把停顿时间控制在MaxGCPauseMills以内,收集器在⼯作时会调整java堆⼤⼩或者
    其他⼀些参数。
  • 对于⽤户来说,停顿时间越短体验越好。但是在服务器端,我们注重⾼并发,整体的吞吐量。所以服务器端适合Parallel,进⾏控制。
  • 该参数使⽤需谨慎

-XX:GCTimeRatio垃圾收集时间占总时间的⽐例(=1/(N+1))。⽤户衡量吞吐量的⼤⼩。

  • 取值范围(0,100)。默认值99,也就是垃圾回收时间不超过1%。
  • 与前⼀个-XX:MaxGCPauseMillis参数有⼀定的⽭盾性。暂停时间越⻓,Radio参数就容易超过设定的⽐例。
~ jinfo -flag GCTimeRatio 19231
-XX:GCTimeRatio=99

Parallel Old (标记-整理算法)

⽼年代并⾏收集器,吞吐量优先,Parallel Scavenge收集器的⽼年代版本;
适⽤场景

  • 与Parallel Scavenge收集器搭配使⽤;注重吞吐量。
    jdk7、jdk8 默认使⽤该收集器作为⽼年代收集器,使⽤ -XX:+UseParallelOldGC 来指定使⽤ Paralle Old 收集器。

CMS(Concurrent Mark Sweep)收集器(标记-清除算法)

在JDK1.5时,HotSpot推出了⼀款在强交互应⽤中᯿要的⼀款垃圾收集器: CMS(Concurrent-MarkSweep),这款垃圾收集器是HotSpot虚拟机中第⼀款真正意义上的并发收集器,它第⼀次实现了垃圾收集线程与⽤户线程同时⼯作

CMS收集器是⼀种尽可能缩短⽤户线程的停顿时间(低延迟)收集器,停顿时间越短(低延迟)就越适合
与⽤户交互的程序,良好的响应速度能提升⽤户体验。

⽬前很⼤⼀部分的java应⽤集中在B/S系统的服务端上,这类应⽤尤其重视服务的响应速度,希望系统停顿时间最短,以给⽤户带来较好的体验。CMS收集器就⾮常符合这类应⽤的需求。

它是⼀种并发收集器,采⽤的是标记-清除算法。

JDK1.5中使⽤CMS来收集⽼年代的时候,新⽣代只能选择ParNew或者Serial收集器中的⼀个。在G1出现之前,CMS使⽤还是⾮常⼴泛的。⼀直到今天,仍然有很多系统使⽤CMS GC。

收集过程

整个垃圾收集过程分为 4 个步骤:

  • 1:初始标记(Initial-Mark):在这个阶段中,程序中所有的⽤户线程都会因为STW机制 ⽽出现短暂的暂停,主要任务是 标记⼀下 GC Roots 能直接关联到的对象,⼀旦标记完成就会恢复之前的⽤户线程,由于直接关联对象⽐较⼩,所以速度⾮常快。
  • 2:并发标记(Concurrent-Mark):从 GC Roots 的直接关联对象开始遍历整个对象图的过程,标记出全部的垃圾对象,耗时较⻓。这个过程耗时较⻓但是不需要暂停⽤户线程,可以与垃圾收集线程⼀起并发执⾏。
  • 3:重新标记(Remark):由于在并发标记阶段中,程序的⽤户线程和垃圾收集线程同时运⾏或者交叉运⾏,因此为了修正并发标记期间,因⽤户线程继续运作⽽导致标记产⽣变动的那⼀部分对象的标记记录,这个阶段的停顿时间通常会⽐初始标记极端稍⻓⼀些,但也远⽐并发标记阶段的时间短。
  • 4:并发清除(Current-Sweep):此阶段清理删除标记阶段判断的已经死亡的对象,释放内存空间。由于不需要移动存活对象,所以这个阶段也是可以与⽤户线程同时并发的。

整个过程耗时最⻓的并发标记和并发清除都是和⽤户线程⼀起⼯作,所以从总体上来说,CMS 收集器垃圾收集可以看做是和⽤户线程并发执⾏的。

尽管CMS收集器采⽤的是并发回收(⾮独占式),但是在其初始化标记和᯿新标记这两个阶段中仍然需要执⾏"stop-the-world"机制暂停程序中的⼯作线程,不过暂停时间并不太⻓,因此可以说明⽬前所有的垃圾收集器都做不到完全不需要"stop-the-world",只是尽可能地缩短暂停时间。

由于最耗费时间的并发标记与并发清除阶段都不需要暂停⼯作,所以整体的回收是低停顿的。

另外,由于在垃圾收集阶段⽤户线程没有中断,所以在CMS回收过程中,还应该确保应⽤程序⽤户线程有⾜够的内存可⽤。因此,CMS收集器不能像其他收集器那样等到⽼年代集合完全被填满了再进⾏收集,⽽是当堆内存使⽤率达到某⼀阀值时,便开始进⾏回收。以确保应⽤程序在CMS⼯作过程中依然有⾜够的空间⽀持应⽤程序运⾏。要是CMS运⾏期间预留的内存⽆法满⾜程序需要,就会出现⼀次"Conrurrent Mode Failure"失败,这时虚拟机将启动后备预案:临时启动Serial Old收集器来重新进⾏⽼年的垃圾收集,这样停顿时间就很⻓了。

空闲列表

CMS收集器的垃圾收集算法采⽤是标记-清除算法,这意味这每次执⾏完内存回收后,由于被执⾏内存回收的⽆⽤对象所占⽤的内存空间极有可能是不连续的⼀些内存块,不可能避免的讲会产⽣⼀些内存碎⽚。那么CMS在为新对象分配内存空间时,将⽆法使⽤指针碰撞(Bump the Pointer)技术,⽽只能够选择空闲列表(Free List)执⾏内存分配。
在这里插入图片描述
为对象分配空间的任务等同于把⼀块确定⼤⼩的内存从Java堆中划分出来。假设Java堆中内存是绝对规整的,所有⽤过的内存都放在⼀边,空闲的内存放在另⼀边,中间放着⼀个指针作为分界点的指示器,那所分配内存就仅仅是把那个指针向空闲空间那边挪动⼀段与对象⼤⼩相等的距离,这种分配⽅式称为“指针碰撞”(Bump the Pointer)。如果Java堆中的内存并不是规整的,已使⽤的内存和空闲的内存相互交错,那就没有办法简单地进⾏指针碰撞了,虚拟机就必须维护⼀个列表,记录上哪些内存块是可⽤的,在分配的时候从列表中找到⼀块⾜够⼤的空间划分给对象实例,并更新列表上的记录,这种分配⽅式称为“空闲列表”(FreeList)。选择哪种分配⽅式由Java堆是否规整决定,⽽Java堆是否规整⼜由所采⽤的垃圾收集器是否带有压缩整理功能决定。

因此,在使⽤Serial、ParNew等带Compact过程的收集器时,系统采⽤的分配算法是指针碰撞,⽽使⽤CMS这种基于Mark-Sweep算法的收集器时,通常采⽤空闲列表。

有⼈会觉得既然Mark Sweep会造成内存碎⽚,那么为什么不把算法换成Mark Compact?

  • 答案其实很简单,因为当并发清除的时候,⽤Compact整理内存的话,原来的⽤户线程使⽤的内存
    还怎么⽤?要保证⽤户线程能继续执⾏,前提是它允许的资源不受影响。
主要优缺点

优点

  • 并发收集;
  • 低停顿;

缺点

  • CMS收集器对CPU资源⾮常敏感。在并发阶段,它虽然不会导致⽤户线程停顿,但是会因为占⽤了⼀部分线程⽽导致应⽤程序变慢,总吞吐量会降低。
  • CMS收集器⽆法处理浮动垃圾,可能出现“Concurrent Mode Failure”失败⽽导致另⼀次Full GC的产⽣。由于CMS并发清理阶段⽤户线程还在运⾏着,伴随程序运⾏⾃然就还会有新的垃圾不断产⽣,这部分垃圾出现在标记过程之后,CMS⽆法在当次收集中处理掉它们,只好留待下⼀次GC时再清理掉。这⼀部分垃圾就称为“浮动垃圾”。
  • CMS是基于“标记-清除”算法实现的收集器,收集结束时会有⼤量空间碎⽚产⽣。空间碎⽚过多,可能会出现⽼年代还有很⼤空间剩余,但是⽆法找到⾜够⼤的连续空间来分配当前对象,不得不提前出发FullGC。
常⽤参数设置

https://docs.oracle.com/javase/8/docs/technotes/tools/unix/java.html

  • 1:-XX:+UseConcMarkSweepGC ⼿动执⾏使⽤CMS收集器执⾏内存回收任务。开启该参数后会⾃动将-XX:+UseParNewGC打开。即:ParNew(Young区) + CMS(Old区) + Serial Old的组合
import java.util.ArrayList;
import java.util.List;
/**
* -XX:+PrintCommandLineFlags -XX:+UseConcMarkSweepGC
*/
public class GCUseTest {
	public static void main(String[] args) {
		List<byte[]> list = new ArrayList<>();
		while (true){
			byte[] arr = new byte[100];
			list.add(arr);
			try {
				Thread.sleep(10);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
}
  • 2: -XX:CMSInitiatingOccupancyFraction 设置堆内存使⽤率的阀值,⼀旦达到该阀值,便开始进⾏回收。
    -XX:CMSInitiatingOccupancyFraction=20 设置到20%时
    如果内存增⻓缓慢,则可以设置⼀个稍⼤的值,⼤的阀值可以有效降低CMS的触发频率,检索⽼年代回收的次数可以较为明显的改善应⽤程序性能。
    相反,如果应⽤程序内存使⽤率增⻓很快,则应该降低这个阀值,以避免频繁触发⽼年代串⾏收集器。通过该选项可以有效降低Full GC的执⾏次数。

  • 3: -XX:+UseCMSCompactAtFullCollection ⽤于指定在执⾏完FullGC后对内存空间进⾏压缩整理,以此避免内存碎⽚的产⽣。不过由于内存压缩整理过程⽆法并发执⾏,所带来的问题就是停顿时间变得更⻓了。

  • 4:-XX:CMSFullGCsBeforeCompaction设置在执⾏多少次Full GC后对内存空间进⾏验收整理

  • 5:-XX:ParallelCMSThreads设置CMS的线程数量
    CMS默认启动的线程数(CPU数量+3)/4,ParallelGCThreads,ParallelGCThreads是年轻代并⾏收集器的线程数。当CPU资源⽐较紧张时,收到CMS收集器线程的影响,应⽤程序的性能在垃圾回收阶段可能会⾮常糟糕

⼩结

HotSpot这么多的垃圾回收器,Serial/Serial Old、Parallel GC、CMS这些GC有什么不同吗?
如果你想要最⼩化地使⽤内存和并⾏开销,请选择Serial Old(⽼年代) + Serial(年轻代)
如果你想要最⼤化应⽤程序的吞吐ᰁ,请选择Parallel Old(⽼年代) + Parallel(年轻代)
如果你想要最⼩化GC的中断或停顿时间,请选择CMS(⽼年代) + ParNew(年轻代)

后续版本

  • JDK9新特性:CMS被标记废弃(Deprecate)了,如果对JDK9 以以上版本的Hotspot虚拟机使⽤-XX:+UseConcMarkSweepGC参数来开启CMS收集器的话,⽤户会收到⼀个警告信息,同时CMS在未来将会被废弃
  • JDK14新特性:删除CMS垃圾回收器,移除CMS垃圾收集器,如果在JDK14中使⽤XX:+UseConcMarkSweepGC的话,JVM不会报错,只是给出⼀个warning信息,但是不会exit,JVM会⾃动回退以默认GC的⽅式启动JVM。

G1(Garbage First)收集器 (区域化分代式)

既然已经有了前⾯的⼏个强⼤的GC,为什么还要发布Garbage First(G1)GC ?

原因在于应⽤程序所对应的业务越来越庞⼤、复杂、⽤户越来越多,没有GC就不能保证应⽤程序正常运⾏,⽽经常造成STW的GC⼜跟不上实际的需求,所以才会不断地尝试对GC进⾏优化。G1 垃圾回收器是在java7 update4之后引⼊的⼀个新的垃圾回收器,是当前收集器技术发展的最前沿成果之⼀。G1收集器基于“标记-整理”算法实现,也就是说不会产⽣内存碎⽚。此外,G1收集器不同于之前的收集器的⼀个重要特点是:G1回收的范围是整个Java堆(包括新⽣代,⽼年代),⽽前六种收集器回收的范围仅限于新⽣代或⽼年代。

与此同时,为了适应现在不断扩⼤的内存和不断增加的处理器数量,进⼀步降低暂停时间(pause time),同时兼顾良好的吞吐量。

官⽅给G1设定的⽬标是在延迟可控的情况下获得尽可能⾼的吞吐量,所以才担当起"全功能收集器"的重任与期望。

G1收集器是当今收集器技术发展最前沿的成果,它是⼀款⾯向服务端应⽤的收集器,它能充分利⽤多CPU、多核环境。因此它是⼀款并⾏与并发收集器,并且它能建⽴可预测的停顿时间模型。

为什么叫做Garbage First(G1)呢?

因为G1是⼀个并⾏回收器,它⽤堆内存分割为很多不相关的区域(Region)(物理上是不连续的)。使⽤不同的Region来表示Eden、survivor、old等。
在这里插入图片描述
G1 GC有计划的避免在整个java堆中进⾏全区域的垃圾收集。G1 跟踪各个Region⾥⾯的垃圾堆积的价值⼤⼩(回收所获得的空间⼤⼩以及回收所需时间的经验值),在后台维护⼀个优先列表,每次根据允许的收集时间,优先回收价值最⼤的Region。

由于这种⽅式的侧重点在于回收垃圾最⼤ᰁ的区间(Region),所以我们给G1⼀个名字:垃圾优先(Garbage First)

G1是⼀款⾯向服务端应⽤的垃圾收集器,主要针对配备多核CPU及⼤容量内存的机器,以极⼤概率满⾜GC停顿时间的同时,还兼具⾼吞吐量的性能特性。

在JDK1.7版本正式启⽤,移除了Experimetal的标识,是JDK9以后的默认垃圾回收器,取代了CMS回收器以及Parallel + ParallelOld 组合。被Oracle官⽅称为"全功能的垃圾收集器"。

与此同时,CMS已经在JDK9中被标记为废弃(deprecated)。在jdk8中还不是默认的垃圾回收器,需要使⽤-XX:+UseG1GC来启⽤。

G1收集器的优点

与其他GC收集器相⽐,G1使⽤全新的分区算法,其特点如下所示:

并⾏与并发

并⾏性: G1在回收期间,可以有多个GC线程同时⼯作,有效利⽤多核计算能⼒。此时⽤户线程STW。

并发性:G1拥有与⽤户线程交替执⾏的能⼒,部分⼯作可以和应⽤程序同时执⾏。因此,⼀般来说,不会再整个回收阶段发⽣完全阻塞应⽤程序的情况。

分代收集

从分代上看,G1依然属于分代⾏垃圾回收器,它会区分年轻代和⽼年代,年轻代依然有Eden区和Survivor区。但从堆的结构上,它不要求整个Eden区、Survivor区或者⽼年代都是联系的,也不再坚持固定⼤⼩和固定数量。

将堆空间划分为若⼲个区域(Region),这些区域包含了逻辑上的年轻代和⽼年代。

和之前的各类垃圾回收器不同,它同时兼顾年轻代和⽼年代。对⽐其他回收器,或者⼯作在年轻代,或者⼯作在⽼年代。

可预测的停顿时间模型(即:软实时 soft real time)

G1会通过⼀个合理的计算模型,计算出每个Region的收集成本并ᰁ化,这样⼀来,收集器在给定了“停顿”时间限制的情况下,总是能选择⼀组恰当的Regions作为收集⽬标,让其收集开销满⾜这个限制条件,以此达到实时收集的⽬的。G1收集器之所以能建⽴可预测的停顿时间模型,是因为它可以有计划地避免在整个java堆中进⾏全区域的垃圾收集。

  • 由于分区原因,G1可以只选取部分区域进⾏内存回收,这样缩⼩了回收的范围,因此对于全局停顿的发⽣也能得到较好的控制。
  • G1跟踪各个Reion⾥⾯的垃圾堆积的价值⼤⼩(回收所获得的空间⼤⼩以及回收所需的时间的经验值),在后台维护⼀个优先列表,每次根据允许的收集时间,优先回收价值最⼤的Region。保证了G1收集器在优先的时间内可以获取尽可能⾼的收集效率。
  • 相⽐于CMS GC,G1未必能做到CMS在最好情况下的延时停顿,但是最差情况要好很多。

如何建⽴可靠的停顿预测模型(满⾜⽤户设定的期望停顿时间)?

G1 收集器的停顿模型是以衰减均值(Decaying Average)为理论基础来实现的:垃圾收集过程中,G1 收集器会根据每个 Region 的回收耗时、记忆集中的脏卡数ᰁ等,分析得出平均值、标准偏差等。

“衰减平均值”⽐普通的平均值更能准确地代表“最近的”平均状态,通过这些信息预测现在开始回收的话,由哪些 Region 组成回收集才能在不超期望停顿时间的约束下获得最⾼收益。

G1收集器的缺点

相对于CMS,G1还不具备全⽅位、压倒性优势。⽐如在⽤户程序运⾏过程中,G1⽆论是为垃圾收集产⽣的内存占⽤还是程序运⾏时的额外执⾏负载都要⽐CMS要⾼。

  • ⼩内存应⽤上,CMS ⼤概率会优于 G1;
  • ⼤内存应⽤上,G1 则很可能更胜⼀筹。
  • 这个临界点⼤概是在 6~8G 之间(经验值)
G1回收器的参数设置

参考文档:https://docs.oracle.com/javase/8/docs/technotes/tools/unix/java.html
-XX:+UseG1GC ⼿动指定使⽤G1收集器执⾏内存回收任务。

‐XX:G1HeapRegionSize 设置每个Region的⼤⼩。值是2的幂,范围是1MB到32MB之间,⽬标是根据最⼩的java堆⼤⼩划分出约2048个区域。默认是堆内存的1/2000。

‐XX:MaxGCPauseMillis 设置期望达到的最⼤GC停顿时间指标(JVM会尽⼒实现,但不保证达到)。默认200ms。

~ jinfo -flag MaxGCPauseMillis 21252
-XX:MaxGCPauseMillis=200

‐XX:InitiatingHeapOccupancyPercent 设置触发并发GC周期的java堆占⽤阀值。超过此值,就触发GC。默认值是45。

~ jinfo -flag InitiatingHeapOccupancyPercent 21252
-XX:InitiatingHeapOccupancyPercent=45

-XX:ParallelGCThread 设置STW⼯作线程数的值。最多设置为8
-XX:ConcGCThreads 设置并发标记的线程数。设置为CPU数ᰁ的1/4左右。

如何设置

G1的设计原则就是简化JVM性能调优,只需要简单三步即可完成:

  • 1:开启G1垃圾收集器 (-XX:+UseG1GC)
  • 2:设置堆的最⼤内存( -Xmx -Xms)
  • 3:设置最⼤的停顿时间(‐XX:MaxGCPauseMillis)
收集过程

G1 收集器收集器收集过程有初始标记、并发标记、最终标记、筛选回收,和 CMS 收集器前⼏步的收集过程很相似:

  • 1:初始标记:标记出 GC Roots 直接关联的对象,这个阶段速度较快,需要停⽌⽤户线程,单线程执⾏
  • 2:并发标记:从 GC Root 开始对堆中的对象进⾏可达性分析,找出存活对象,这个阶段耗时较⻓,但可以和⽤户线程并发执⾏
  • 3:最终标记:修正在并发标记阶段由于⽤户程序执⾏⽽产⽣变动的标记记录
  • 4:筛选回收:筛选回收阶段会对各个 Region 的回收价值和成本进⾏排序,根据⽤户所期望的 GC停顿时间来指定回收计划(⽤最少的时间来回收包含垃圾最多的区域,这就是 Garbage First 的由来——第⼀时间清理垃圾最多的区块),这⾥为了提⾼回收效率,并没有采⽤和⽤户线程并发执⾏的⽅式,⽽是停顿⽤户线程。
G1回收器的使⽤场景

1:⾯向服务端应⽤,针对具有⼤内存、多处理器的机器。(在普通⼤⼩的堆⾥表现并不惊喜)
2:最主要的应⽤是需要低GC延迟,并具有⼤堆的应⽤程序提供解决⽅案;
3:在堆⼤⼩约6GB或更⼤时,可预测的暂停时间可以低于0.5秒;(G1通过每次只清理⼀部分⽽不是全部Region的增量式清理在保证每次GC停顿时间不会过⻓) 。
4:⽤来替换掉JDK1.5中的CMS收集器,以下情况,使⽤G1可能⽐CMS好

  • 超过50% 的java堆被活动数据占⽤;
  • 对象分配频率或年代提升频率变化很⼤;
  • GC停顿时间过⻓(⼤于0.5⾄1秒)
    5:HotSpot垃圾收集器⾥,除了G1以外,其他的垃圾收集器使⽤内置的JVM线程执⾏GC多线程操作,⽽G1 GC可以采⽤应⽤线程运⾏GC的⼯作,即当JVM的GC线程处理速度慢时,系统会调⽤应⽤程序帮助加速垃圾回收过程。
Region的使⽤介绍

分区Region:化整为零
使⽤G1收集器是,它将整个java堆划分为约2048个⼤⼩相同的独⽴Region块,每个Region块⼤⼩根据堆空间的实际⼤⼩⽽定,整体被控制在1mb到32mb之间,且为2的N次幂,即1mb、2mb、4mb、8mb、16mb、32mb。可以通过‐XX:G1HeapRegionSize设定。所有的Region⼤⼩相同,且在JVM⽣命周期内不会被改变。

虽然还保留这新⽣代和⽼年代的概念,但新⽣代和⽼年代不再是物理隔离的了,它们都是⼀部分Region(不需要连续)的集合。通过Region的动态分配⽅式实现逻辑上的连续。
在这里插入图片描述
⼀个region有可能属于Eden,Survivor,或者Old 内存区域。但是⼀个region只可能属于⼀个⻆⾊。图中的E表示region属于Eden内存区域,S表示属于Survivor内存区域,O表示属于Old内存区域。图中空⽩的表示未使⽤的内存空间。

G1垃圾收集器还增加了⼀种新的内存区域,叫做Humongous内存区域,如图中的H块。主要⽤于存储⼤对象,如果超过1.5个region,就放到H区。

设置H的原因:

  • 对于堆中的⼤对象,默认直接会分配到⽼年代,但是如果他是⼀个短期存在的⼤对象,就会对垃圾收集器造成负⾯影响。为了解决这个问题,G1划分了⼀个Humongous区,它⽤来专⻔存放⼤对象。如果⼀个H区装不下⼀个⼤对象,那么G1会寻找连续的H区来存储。为了能找到连续的H区,有时候不得不启动Full GC。G1的⼤多数⾏为都把H区作为⽼年代⼀部分来看待。
主要回收环节

G1 GC的垃圾回收过程主要包含以下三个环节:

  • 年轻代GC (Young GC)
  • ⽼年代并发标记过程(Concurrent Marking)
  • 混合回收(Mixed GC)

如果需要,单线程、独占式、⾼强度的Full GC还是继续存在的。Full GC针对GC 的评估失败提供了⼀种失败的保护机制,即强⼒回收。)
在这里插入图片描述
Young GC -> Young GC + concurrent mark -> mixed GC 顺序,进⾏垃圾回收。
年轻代GC

  • 应⽤程序分配内存,当年轻代的Eden区⽤尽时开始年轻代回收过程;G1的年轻代收集阶段是⼀个并⾏的独占式收集器。在年轻代回收期,G1 GC暂停所有的应⽤程序线程,启动多线程执⾏年轻代回收。然后从年轻代区间移动存活对象到Survivor区间或者⽼年代区间,也有可能是两个区间都会涉及。

⽼年代并发标记(Concurrent Marking)

  • 当堆内存使⽤达到⼀定值(默认是45%)时,开始⽼年代并发标记过程。

混合回收(Mixed GC)

  • 标记完成⻢上开始混合回收过程。对于⼀个混合回收期,G1 GC从⽼年代移动存活对象到空闲区间,这些空闲区间也就成为了⽼年代的⼀部分。和年轻代不同,⽼年代的G1回收器和其他GC不同,G1的⽼年代回收器不需要整个⽼年代被回收,⼀次主要扫描/回收⼀⼩部分⽼年代Region就可以了。同时,这个⽼年代Region是和年轻代⼀起被回收的。

举个示例:⼀个Web服务器,java进程最⼤堆内存为4G,每分钟响应1500个请求,每45秒钟会新分配⼤约2G的内存。G1会每45秒钟进⾏⼀次年轻代回收,每31个⼩时整个堆的使⽤率会达到45%。会开始⽼年代并发标记过程,标记完成后开始四到五次的混合回收。

跨 Region 引⽤对象

⼀个对象被不同区域引⽤的问题

  • ⼀个Region不可能是孤⽴的,⼀个Region中的对象可能被其他任意Region中对象引⽤,判断对象存活时,是否需要扫描整个java堆才能保证准确
  • 在其他的分代收集器,也存在这样的问题,但是在G1更突出
  • 回收新⽣代也不得不同时扫描⽼年代 ,这样的话会降低Young GC的效率
    在这里插入图片描述

解决⽅法:

  • ⽆论是G1还是其他分代收集器,JVM都是使⽤Remembered Set来避免扫描全局扫描
  • 每个Region都有⼀个对应的Remember Set;
  • 每次Reference类型数据写操作时,都会产⽣⼀个Write Barrier(写屏障)暂时中断操作;
    写屏障是指,在改变特定内存的值(实际上也就是写⼊内存)的时候额外执⾏的⼀些动作。
    在⼤多数的垃圾回收算法中,都利⽤到了写屏障。
    G1的垃圾回收器的写屏障使⽤⼀种两级的log buffer结构:
    • global set of filled buffer:所有线程共享的⼀个全局的,存放填满了的log buffer的集合;
    • thread log buffer:每个线程⾃⼰的log buffer。所有的线程都会把写屏障的记录先放进去⾃⼰的log buffer中,装满了之后,就会把log buffer放到 global set of filledbuffer中,⽽后再申请⼀个log buffer;
  • 然后检查将要写⼊的引⽤指向的对象是否和该Reference类型数据在不同的Region(其他收集器:
    检查⽼年代对象是否引⽤了新⽣代对象);
  • 如果不同,通过CardTable把相关的引⽤信息记录到引⽤指向对象的所在Region对应的Remember Set中;
  • 当进⾏垃圾收集时,在GC根据节点的枚举范围加⼊Remember Set;就可以保证不进⾏全局扫描,也不会有遗漏
跨 Region 引⽤对象

如果⼀个线程修改了Region内部的引⽤,就必须要去通知RS,更改其中的记录。为了达到这种⽬的,G1回收器引⼊了⼀种新的结构,CT(Card Table)——卡表。每⼀个Region,⼜被分成了固定⼤⼩的若⼲张卡(Card)。每⼀张卡,都⽤⼀个Byte来记录是否修改过。卡表即这些byte的集合。实际上,如果把RS理解成⼀个概念模型,那么CT就可以说是RS的⼀种实现⽅式。

在RS的修改上也会遇到并发的问题。因为⼀个Region可能有多个线程在并发修改,因此它们也会并发修改RS。为了避免这样⼀种冲突,G1垃圾回收器进⼀步把RS划分成了多个哈希表。每⼀个线程都在各⾃的哈希表⾥⾯修改。最终,从逻辑上来说,RS就是这些哈希表的集合。哈希表是实现RS的⼀种通常的⽅式之⼀。它有⼀个极⼤的好处就是能够去除᯿复。这意味着,RS的⼤⼩将和修改的指针数量相当。⽽在不去重的情况下,RS的数ᰁ和写操作的数量相当。

G1回收器优化建议

1:年轻代⼤⼩

  • 固定年轻代的⼤⼩会覆盖暂停时间⽬标
  • 避免使⽤-Xmn或者-XX:NewRatio等相关选项显示设置年轻代⼤⼩

2:暂停时间⽬标不要太过严苛

  • 评估G1 GC的吞吐量时,暂停时间⽬标不要太严苛。如果太严苛表示你愿意承受更多的垃圾回收开
    、销,⽽这样会直接影响吞吐量
  • G1 GC的吞吐量⽬标是90%的应⽤程序时间和10%的垃圾回收时间

从Oracle官⽅透露出来的信息可知,回收阶段(Evacuation)其实本也有想过设计成与⽤户⼀起并发执⾏,但这件事情做起来⽐较复杂,考虑到G1只是回收⼀部分Region,停顿时间是⽤户可控制的,所以并不迫切去实现,⽽选择把这个特性放到了G1之后出现的低延迟垃圾收集器(即ZGC)中。另外,还考虑到G1不是仅仅⾯向低延迟,停顿⽤户线程能够最⼤幅度提⾼垃圾收集效率,为了保证吞吐ᰁ所以才选择了完全暂停⽤户线程的实现⽅案。

垃圾回收器总结

GC 发展阶段:Serial => Parallel(并⾏) => CMS(并发) => G1 => ZGC
截⽌jdk1.8 ,⼀共有7款不同垃圾收集器。每⼀款不同的垃圾收集器都有不同的特点,在具体使⽤的时候,需要根据具体的情况选择不同的垃圾回收器

垃圾收集器分类作用位置使用方法特点使用场景
Serial串⾏新⽣代复制算法响应速度优先适⽤于单CPU环境下client模式
ParNew并⾏新⽣代复制算法响应速度优先多CPU环境下Server模式下与CMS配合
Parallel并⾏新⽣代复制算法吞吐量优先适⽤于后台运算⽽不需要太多交互的场景
Serial Old串⾏⽼年代标记-压缩响应速度优先适⽤于单CPU环境下client模式
Parallel Old并⾏⽼年代标记-压缩吞吐量优先适⽤于后台运算⽽不需要太多交互的场景
CMS并发⽼年代标记-清除响应速度优先适⽤于互联⽹或B/S业务
G1并发、并⾏新、老复制;标记-清除响应速度优先⾯向服务端应⽤
垃圾收集器的新发展

GC仍然处于⻜速发展之中,⽬前的默认选项G1 GC在不断的进⾏改进,很多我们原来认为的缺点,都已经被⼤幅改进,例如:JDK 10以后,串⾏Full GC已经是并⾏运⾏,在很多场景下,其表现略优于Parallel GC的并⾏Full GC实现。

即使是Serial GC,虽然⽐较古⽼,但是简单的设计和实现未必就是过时的,它本身的开销,不管是GC相关数据结构的开销,还是线程的开销,都是⾮常⼩的,所以随着云计算的兴起,在Serverless等新的应⽤场景下,Serial GC找到了新的舞台

⽐较不幸的是CMS GC,因为其算法的理论缺陷等原因,虽然现在还有⾮常⼤的⽤户群体,但是JDK9中已经被标记为废弃,并在JDK14版本中移除

现在G1回收器已经成为默认回收器好⼏年了,我们还看到了引⼊两个新的收集器:ZGC(JDK11出现) 和Shenandoah(Open JDK12)。主打特点:低停顿时间

Shenandoah

  • Shenandoah,⽆疑是众多GC中最孤独的⼀个。是第⼀款不由Oracle公司团队领导开发的HotSpot垃圾收集器。不可避免的收到官⽅的排挤。⽐如号称OpenJDK和OracleJDK没有区别的Oracle公司仍拒绝在OracleJDK12中⽀持Shenandoah。
  • Shenandoah垃圾回收器最初由RedHat进⾏的⼀项垃圾收集器研究项⽬Pauseless GC的实现,旨在针对JVM上的内存回收实现低停顿的需求。在2014年贡献给OpenJDK。
  • Red Hat研究Shenandoah团队对外宣称,Shenandoah垃圾回收器的暂停时间与堆⼤⼩⽆关,这意味这⽆论将堆设置为200MB还是200GB,99.9%的⽬标都可以把垃圾收集的停顿时间限制在⼗毫秒以内。不过实际使⽤性能取决于实际⼯作堆的⼤⼩和⼯作负载。
垃圾收集器运⾏时间总停顿最⼤停顿平均停顿
Shenandoah387.602s320ms89.79ms53.01ms
G1312.052s11.7s1.24s450.12ms
CMS285.264s12.78s4.39s852.ms
Parallel Scavenge260.092s6.59s3.04s823.75ms

这是RedHat在2016年发表的论⽂数据,测试内容是使⽤ES对200GB的维基百科数据进⾏索引。从结果看:

  • 停顿时间⽐其他⼏款收集器确实有了质的的⻜跃,但也未实现最⼤停顿时间控制在⼗毫秒以内的⽬标。
  • ⽽吞吐量⽅⾯出现了明显的下降,总运⾏时间是所有测试收集器⾥最⻓的。
ZGC

ZGC: A Scalable Low-Latency Garbage Collector (Experimental)(ZGC: 可伸缩的低延迟垃圾回收器,处于实验性阶段) http://openjdk.java.net/jeps/333

ZGC的⽬标是:在尽可能对吞吐量影响不⼤的前提下,实现任意堆内存⼤⼩下都可以把垃圾收集的停顿时间限制控制在⼗毫秒以内的低延迟。

《深⼊理解java虚拟机》⼀书中这样定义ZGC:ZGC收集器是⼀款基于Region内存布局的,(暂时)不设分代的,使⽤了读屏障、染⾊指针的内存多᯿映射等技术来实现可并发的标记-压缩算法的,以低延迟为⾸要⽬标的⼀款垃圾收集器。

ZGC的⼯作过程可以分为4个阶段:并发标记-并发预备重分配-并发重分配-并发重映射等

ZGC⼏乎在所有地⽅都是并发执⾏的,除了初始标记是STW的。所有停顿时间⼏乎就耗费在初始标记上,这部分的实际时间是⾮常少的。

虽然ZGC还在试验阶段,没有完成所有特性,但此时性能已经相当亮眼,⽤"令⼈震惊、⾰命性"来形容,都不为过。

未来将在服务端、⼤内存、低延迟应⽤的场景下⾸选垃圾收集器。

JDK14之前,ZGC仅在Linux才⽀持

尽管许多使⽤ZGC的⽤户都使⽤类Linux的环境,但在Windows和macOS上,⼈们也需要ZGC进⾏开发部署和测试。许多桌⾯应⽤也可以从ZGC中受益。因此,ZGC特性被移植到了Windows和macOS上。

现在mac或Windows上也能使⽤ZGC了,参数配置如下:

-XX:+UnlockExperimentalVMOptions -XX:+UseZGC
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值