0
点赞
收藏
分享

微信扫一扫

JVM七种垃圾回收器

什么是垃圾回收器

如果说收集算法是内存回收的方法论,那么垃圾收集器就是内存回收的具体实现。
虽然我们对各个收集器进行比较,但并非要挑选出一个最好的收集器。因为直到现在为止还没有最好的垃圾收集器出现,更加没有万能的垃圾收集器,我们能做的就是根据具体应用场景选择适合自己的垃圾收集器。试想一下:如果有一种四海之内、任何场景下都适用的完美收集器存在,那么我们的 HotSpot 虚拟机就不会实现那么多不同的垃圾收集器了。

在JVM中间一般来说垃圾回收器不单单是一个算法,也就是说在JVM垃圾回收器中可能多种算法都有用到

垃圾回收器汇总

新生代垃圾回收器汇总

JVM七种垃圾回收器_垃圾回收器

新生代垃圾回收器汇总

所有的新生代的垃圾回收器都是复制算法

收集器

收集对象和算法

收集器类型

Serial

新生代,复制算法

单线程

ParNew

新生代,复制算法

并行的多线程收集器

Parallel Scavenge

新生代,复制算法

并行的多线程收集器

老年代垃圾回收器汇总

老年代会有两种算法,标记整理算法 和 标记清除算法

收集器

收集对象和算法

收集器类型

Serial Old

老年代,标记整理算法

单线程

Parallel Old

老年代,标记整理算法

并行的多线程收集器

CMS

老年代,标记清除算法

并行与并发收集器

G1

跨新生代和老年代;标记整理 + 化整为零

并行与并发收集器

Serial/Serial Old(串行收集器)

最古老的,单线程串行垃圾回收器,使用复制算法进行垃圾回收,独占式,GC时需要暂停所有用户线程,直到GC完成,但是比较成熟,适合单CPU 服务器

-XX:+UseSerialGC 新生代和老年代都用串行收集器
-XX:+UseParNewGC 新生代使用ParNew,老年代使用Serial Old
-XX:+UseParallelGC 新生代使用ParallerGC,老年代使用Serial Old

Serial(串行)收集器收集器是最基本、历史最悠久的垃圾收集器了。它的 “单线程” 的意义不仅仅意味着它只会使用一条垃圾收集线程去完成垃圾收集工作,更重要的是它在进行垃圾收集工作的时候必须暂停其他所有的工作线程( “Stop The World” ),直到它收集结束。

新生代采用复制算法,老年代采用标记-整理算法.

虚拟机的设计者们当然知道 Stop The World 带来的不良用户体验,所以在后续的垃圾收集器设计中停顿时间在不断缩短(仍然还有停顿,寻找最优秀的垃圾收集器的过程仍然在继续)。

但是 Serial 收集器有没有优于其他垃圾收集器的地方呢?当然有,它简单而高效(与其他收集器的单线程相比)。Serial 收集器由于没有线程交互的开销,自然可以获得很高的单线程收集效率。Serial 收集器对于运行在 Client 模式下的虚拟机来说是个不错的选择。
JVM七种垃圾回收器_垃圾回收器_02

ParNew (并行收集器)

和Serial基本没区别,唯一的区别:多线程,多CPU的,停顿时间比Serial少

ParNew 收集器其实就是 Serial 收集器的多线程版本,除了使用多线程进行垃圾收集外,其余行为(控制参数、收集算法、回收策略等等)和 Serial 收集器完全一样

-XX:+UseParNewGC 新生代使用ParNew,老年代使用Serial Old
除了性能原因外,主要是因为除了 Serial 收集器,只有它能与 CMS 收集器配合工作。

ParNew 收集器其实就是 Serial 收集器的多线程版本,除了使用多线程进行垃圾收集外,其余行为(控制参数、收集算法、回收策略等等)和 Serial 收集器完全一样。

新生代采用复制算法,老年代采用标记-整理算法。
JVM七种垃圾回收器_垃圾回收器_03

它是许多运行在 Server 模式下的虚拟机的首要选择,除了 Serial 收集器外,只有它能与 CMS 收集器(真正意义上的并发收集器,后面会介绍到)配合工作。

并行和并发概念补充:

并行(Parallel) :指多条垃圾收集线程并行工作,但此时用户线程仍然处于等待状态。
并发(Concurrent):指用户线程与垃圾收集线程同时执行(但不一定是并行,可能会交替执行),用户程序在继续运行,而垃圾收集器运行在另一个 CPU 上。

Parallel Scavenge(ParallerGC)/Parallel Old (并行收集器)

Parallel Scavenge 收集器也是使用复制算法的多线程收集器,它看上去几乎和ParNew都一样。 那么它有什么特别之处呢?

Parallel Scavenge垃圾收集器(类似于ParNew,但是该收集器关注的是cpu的吞吐量,通过参数来控制吞吐量,是吞吐量优先的收集器,同样使用复制算法进行垃圾回收,GC过程需要暂停所有用户线程。)

采用的是复制算法,回收的是新生代

特点:
关注吞吐量的垃圾收集器,高吞吐量则可以高效率地利用CPU时间,尽快完成程序的运算任务,主要适合在后台运算而不需要太多交互的任务。
所谓吞吐量就是CPU用于运行用户代码的时间与CPU总消耗时间的比值,即吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间),虚拟机总共运行了100分钟,其中垃圾收集花掉1分钟,那有吞吐效率就是99%。

Parallel Scavenge 收集器关注点是吞吐量(高效率的利用 CPU)。CMS 等垃圾收集器的关注点更多的是用户线程的停顿时间(提高用户体验)。所谓吞吐量就是 CPU 中用于运行用户代码的时间与 CPU 总消耗时间的比值。 Parallel Scavenge 收集器提供了很多参数供用户找到最合适的停顿时间或最大吞吐量,如果对于收集器运作不太了解的话,手工优化存在的话可以选择把内存管理优化交给虚拟机去完成也是一个不错的选择。

新生代采用复制算法,老年代采用标记-整理算法。
JVM七种垃圾回收器_垃圾回收器_03

Concurrent Mark Sweep (CMS)

CMS只会收集老年代的内容,只是对单个的区域进行回收,采用的算法是标记清除算法.
CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。它而非常符合在注重用户体验的应用上使用。
CMS(Concurrent Mark Sweep)收集器是 HotSpot 虚拟机第一款真正意义上的并发收集器,它第一次实现了让垃圾收集线程与用户线程(基本上)同时工作。

收集器是一种以获取最短回收停顿时间为目标的收集器。目前很大一部分的Java应用集中在互联网站或者B/S系统的服务端上,这类应用尤其重视服务的响应速度,希望系统停顿时间最短,以给用户带来较好的体验。CMS收集器就非常符合这类应用的需求。

-XX:+UseConcMarkSweepGC ,一般新生代使用ParNew,老年代的用CMS
从名字(包含“Mark Sweep”)上就可以看出,CMS收集器是基于“标记—清除”算法实现的,它的运作过程相对于前面几种收集器来说更复杂一些,

并行收集: CMS首先是多线程的,
并发收集: 同时垃圾收集的多线程和应用的多线程同时进行.

1.垃圾回收过程

​​https://www.yuque.com/docs/share/50dd0d21-d541-42e9-a376-e776bbeaa865?#​​ 《CMS垃圾回收器步骤》

2.优点

并发收集、低停顿

在使用CMS的时候,用户线程只有在初始标记和重新标记的时候才会暂停,并且占据的时间非常的短,
CMS寻求的是最短的暂停时间为目的的一个收集器.

由于整个过程中耗时最长的并发标记和并发清除过程收集器线程都可以与用户线程一起工作,所以,从总体上来说,CMS收集器的内存回收过程是与用户线程一起并发执行的。

很多Java程序用CMS还是非常多的,因为CMS垃圾回收器非常的关注响应速度.

3.缺点

对CPU的资源是要求比较高的,CPU资源敏感:因为并发阶段多线程占据CPU资源,如果CPU资源不足,效率会明显降低。

浮动垃圾
在进行并发的标记后,开始并发的清理的时候,此时是和用户线程一起运行的,
在执行并发清理的时候,其它用户线程还会有可能产生垃圾.这个时候这次的回收是无法回收这些后来产生的垃圾的,必须等待下一次进行垃圾回收的时候才能清理此时用户产生的垃圾.

由于浮动垃圾的存在,因此需要预留出一部分内存,意味着 CMS 收集不能像其它收集器那样等待老年代快满的时候再回收。(其它垃圾回收器是启动的时候就暂停用户线程,所以不会出现浮动垃圾的情况)

在1.6的版本中老年代空间使用率阈值(92%),就会开启垃圾回收,而不是达到100%的时候才会开启垃圾回收
如果预留的内存不够存放浮动垃圾,就会出现 Concurrent Mode Failure,这时虚拟机将临时启用 Serial Old 来替代 CMS。

会产生空间碎片:
标记 - 清除算法会导致产生不连续的空间碎片

4.Stop The World现象

CMS并非没有暂停,而是用两次短暂停来替代串行标记整理算法的长暂停。

内外的设置正常收集周期是这样的:

1)CMS-initial-mark 初始标记(会stop-the-world)
  2)CMS-concurrent-mark 并发标记的
  3)CMS-concurrent-preclean 执行预清理 注: 相当于两次 concurrent-mark. 因为上一次c mark,太长.会有很多 changed object 出现.先干掉这波.到最好的 stop the world 的 remark 阶段,changed object 会少很多.
  4)CMS-concurrent-abortable-preclean 执行可中止预清理
  5)CMS-remark 重新标记(会stop-the-world)
  6)CMS-concurrent-sweep 并发清除
  7)CMS-concurrent-reset 并发重设状态等待下次CMS的触发

为什么 CMS两次标记时要 stop the world?

我们知道垃圾回收首先是要经过标记的。对象被标记后就会根据不同的区域采用不同的收集方法。看上去很完美的一件事情,其实并不然。
  大家有没有想过一件事情,当虚拟机完成两次标记后,便确认了可以回收的对象。但是,垃圾回收并不会阻塞我们程序的线程,他是与当前程序并发执行的。所以问题就出在这里,当GC线程标记好了一个对象的时候,此时我们程序的线程又将该对象重新加入了“关系网”中,当执行二次标记的时候,该对象也没有重写finalize()方法,因此回收的时候就会回收这个不该回收的对象。
  虚拟机的解决方法就是在一些特定指令位置设置一些“安全点”,当程序运行到这些“安全点”的时候就会暂停所有当前运行的线程(Stop The World 所以叫STW),暂停后再找到“GC Roots”进行关系的组建,进而执行标记和清除。
  这些特定的指令位置主要在:

1、循环的末尾
2、方法临返回前 / 调用方法的call指令后
3、可能抛异常的位置

First(G1)垃圾回收器

G1 (Garbage-First)是一款面向服务器的垃圾收集器,主要针对配备多颗处理器及大容量内存的机器. 以极高概率满足GC停顿时间要求的同时,还具备高吞吐量性能特征.

被视为 JDK1.7 中 HotSpot 虚拟机的一个重要进化特征。它具备一下特点:

并行与并发:G1 能充分利用 CPU、多核环境下的硬件优势,使用多个 CPU(CPU 或者 CPU 核心)来缩短 Stop-The-World 停顿时间。部分其他收集器原本需要停顿 Java 线程执行的 GC 动作,G1 收集器仍然可以通过并发的方式让 java 程序继续执行。
分代收集:虽然 G1 可以不需要其他收集器配合就能独立管理整个 GC 堆,但是还是保留了分代的概念。
空间整合:与 CMS 的“标记–清理”算法不同,G1 从整体来看是基于“标记整理”算法实现的收集器;从局部上来看是基于“复制”算法实现的。
可预测的停顿:这是 G1 相对于 CMS 的另一个大优势,降低停顿时间是 G1 和 CMS 共同的关注点,但 G1 除了追求低停顿外,还能建立可预测的停顿时间模型,能让使用者明确指定在一个长度为 M 毫秒的时间片段内。

G1也是关注最小时延的垃圾回收器,也同样适合大尺寸堆内存的垃圾收集,官方也推荐使用G1来代替选择CMS.

G1垃圾回收器的内存格局已经改变了,在以前的很多种垃圾回收器回收都是有一个回收新生代,一个回收老年代的,它们是各自有各自的专职的,但是在G1垃圾回收器,它跨域了两个区域,在新生代和老年代都可以进行回收,它的思想是化整为零.

G1中重要的参数:
-XX:+UseG1GC 使用G1垃圾回收器

1.G1收集器特点

• 并行与并发:G1 能充分利用 CPU、多核环境下的硬件优势,使用多个 CPU(CPU 或者 CPU 核心)来缩短 Stop-The-World 停顿时间。部分其他收集器原本需要停顿 Java 线程执行的 GC 动作,G1 收集器仍然可以通过并发的方式让 java 程序继续执行。
• 分代收集:虽然 G1 可以不需要其他收集器配合就能独立管理整个 GC 堆,但是还是保留了分代的概念。
• 空间整合:与 CMS 的“标记–清理”算法不同,G1 从整体来看是基于“标记整理”算法实现的收集器;从局部上来看是基于“复制”算法实现的。
• 可预测的停顿:这是 G1 相对于 CMS 的另一个大优势,降低停顿时间是 G1 和 CMS 共同的关注点,但 G1 除了追求低停顿外,还能建立可预测的停顿时间模型,能让使用者明确指定在一个长度为 M 毫秒的时间片段内。

2.内部布局改变

JVM七种垃圾回收器_垃圾回收_05

G1 把堆划分成多个大小相等的独立区域(Region),新生代和老年代不再物理隔离。

假如说堆内存是8G,G1垃圾回收器把这个容量划分成1000份, 那么每1份大约就是8M的样子,1000份分别有EdenSurvivor Old Humongous 四个区域,这就是化整为零的思想.这样新生代和老年代不再物理隔离。

当然堆内存设置有最小设置,如果基数小的话,化整为零的意义就不大了.就好比你想弄Docker,如果你服务器内存就只有两三个G了,那么做虚拟机的意义就不大了,除非你机器有100多个G,那么就可以化整为零,划成5个虚拟机,划成10个虚拟机都行,如果你机器只有两三个G,那你用Docker是没有太多意义的.

G1垃圾回收器的特点:

  1. 空间整合,不会产生内存碎片(和G1垃圾回收器使用的算法有关)
  2. 可预测的停顿(如果你的垃圾回收器是G1 的话,jvm虚拟机是可以预测处理的你的垃圾回收会停顿多久,可以设置参数,期望停顿的时间是多少,比如期望每一次垃圾回收的时间最长暂停不能超过50毫秒,G1垃圾回收器在垃圾回收的话,就会尽可能的控制在50毫秒之内,但是不一定能做到.)

G1将Java堆划分为多个大小相等的独立区域(Region),JVM最多可以有2048个Region。
一般Region大小等于堆大小除以2048,比如堆大小为4096M,则Region大小为2M,当然也可以
用参数"-XX:G1HeapRegionSize"手动指定Region大小,但是推荐默认的计算方式。
G1保留了年轻代和老年代的概念,但不再是物理隔阂了,它们都是(可以不连续)Region的集
合。
默认年轻代对堆内存的占比是5%,如果堆大小为4096M,那么年轻代占据200MB左右的内存,
对应大概是100个Region,可以通过“-XX:G1NewSizePercent”设置新生代初始占比,在系统
运行中,JVM会不停的给年轻代增加更多的Region,但是最多新生代的占比不会超过60%,可以
通过“-XX:G1MaxNewSizePercent”调整。年轻代中的Eden和Survivor对应的region也跟之前
一样,默认8:1:1,假设年轻代现在有1000个region,eden区对应800个,s0对应100个,s1对应
100个。
一个Region可能之前是年轻代,如果Region进行了垃圾回收,之后可能又会变成老年代,也就是
说Region的区域功能可能会动态变化。
G1垃圾收集器对于对象什么时候会转移到老年代跟之前讲过的原则一样,唯一不同的是对大对象
的处理,G1有专门分配大对象的Region叫Humongous区,而不是让大对象直接进入老年代的
Region中。在G1中,大对象的判定规则就是一个大对象超过了一个Region大小的50%,比如按
照上面算的,每个Region是2M,只要一个大对象超过了1M,就会被放入Humongous中,而且
一个大对象如果太大,可能会横跨多个Region来存放。

Humongous区专门存放短期巨型对象,不用直接进老年代,可以节约老年代的空间,避免因为老
年代空间不够的GC开销。
Full GC的时候除了收集年轻代和老年代之外,也会将Humongous区一并回收.

使用的算法:
标记—整理 (old,humongous) 和复制回收算法(survivor)。

JVM七种垃圾回收器_垃圾回收器_06
新生代的 Eden Survivor
老年代的Old

Humongous 是放大一些的东西

3.G1收集器一次GC的运作过程

初始标记(initial mark,STW):
暂停所有的其他线程,并记录下gc roots直接能引用的对象,速度很快 ;
并发标记(Concurrent Marking):
同CMS的并发标记
最终标记(Remark,STW):
同CMS的重新标记
筛选回收(Cleanup,STW):
筛选回收阶段首先对各个Region的回收价值和成本进行
排序,根据用户所期望的GC停顿时间(可以用JVM参数 -XX:MaxGCPauseMillis指定)来制
定回收计划,比如说老年代此时有1000个Region都满了,但是因为根据预期停顿时间,本
次垃圾回收可能只能停顿200毫秒,那么通过之前回收成本计算得知,可能回收其中800个
Region刚好需要200ms,那么就只会回收800个Region,尽量把GC导致的停顿时间控制在
我们指定的范围内。这个阶段其实也可以做到与用户程序一起并发执行,但是因为只回收一
部分Region,时间是用户可控制的,而且停顿用户线程将大幅提高收集效率。不管是年轻代
或是老年代,回收算法主要用的是复制算法,将一个region中的存活对象复制到另一个
region中,这种不会像CMS那样回收完因为有很多内存碎片还需要整理一次,G1采用复制
算法回收几乎不会有太多内存碎片。

JVM七种垃圾回收器_老年代_07

G1收集器在后台维护了一个优先列表,每次根据允许的收集时间,优先选择回收价值最大的
Region(这也就是它的名字Garbage-First的由来),比如一个Region花200ms能回收10M垃
圾,另外一个Region花50ms能回收20M垃圾,在回收时间有限情况下,G1当然会优先选择后面
这个Region回收。这种使用Region划分内存空间以及有优先级的区域回收方式,保证了G1收集
器在有限时间内可以尽可能高的收集效率。

4.GC模式

Young GC

每一个区域,这个区域可能就代表一个,如果堆有8G,可能垃圾回收器就会划分成1000个区,Young就会对Eden和Survivor区域里面的东西进行对应的回收,使用复制回收算法.

Mixed GC

这个模式的回收,基本就是采取下图的做法
JVM七种垃圾回收器_老年代_08

它主要回收的区域,它就要对这些区域进行标记,它除了对Young选定的所以的Eden和Survivor,这种回收你可以把它想象成类似于所谓的 Full GC,但是和Full GC 有点区别.
首先Mixed GC可以回收Eden,同样还会对Old区域对应的标记,所谓的标记就是全局并发标记

首先会先初始标记,初始标记和CMS的标记其实是差不多的,然后做完以后再做并发标记,并发标记做完以后再做最终标记,然后再来做所谓的回收.

JVM七种垃圾回收器_垃圾回收器_06
上面的四步回收过程(初始标记,并发标记.最终标记,回收),可能会针对堆里面划分好的编号为1024(编号是随机的)独立区域(Region),也可能是编号为9528的独立区域,可能这时候G1线程可能只对1024编号的独立区域进行回收,因为会有优先的顺序
也就是说,如果我采用G1的垃圾回收器,它为什么能够做到可预测的停顿?

G1垃圾回收器会做一个智能的判断,它会进行优化,因为对堆进行划分了区域,所以说可以对优先需要回收的区域进行垃圾回收.

假如说有1000个区域,一个区域回收的时间是10毫秒,这个时候如果我时间卡1秒之内的话,我就只是回收前100个需要优先进行垃圾回收的区域进行垃圾回收.这个时间回收的停顿时间就可以预测了. 当然,任何的垃圾回收器都会有停顿的,没有说不需要停顿的,只是停顿时间的长和短而已.

所以化整为零的好处就是可以在有限的时间内可以获取最高的回收效率.

可预测的停顿:
G1收集器之所以能建立可预测的停顿时间模型,是因为它可以有计划地避免在整个Java堆中进行全区域的垃圾收集。G1跟踪各个Region里面的垃圾堆积的价值大小(回收所获得的空间大小以及回收所需时间的经验值),在后台维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的Region(这也就是Garbage-First名称的来由)。这种使用Region划分内存空间以及有优先级的区域回收方式,保证了G1收集器在有限的时间内可以获取尽可能高的收集效率。
G1把内存“化整为零”的思路

全局并发标记(global concurrent marking)

**初始标记:**仅仅只是标记一下GC Roots 能直接关联到的对象,并且修改TAMS(Nest Top Mark Start)的值,让下一阶段用户程序并发运行时,能在正确可以的Region中创建对象,此阶段需要停顿线程(STW),但耗时很短。

**并发标记:**从GC Root 开始对堆中对象进行可达性分析,找到存活对象,此阶段耗时较长,但可与用户程序并发执行。

**最终标记:**为了修正在并发标记期间因用户程序继续运作而导致标记产生变动的那一部分标记记录,虚拟机将这段时间对象变化记录在线程的 Remembered Set Logs 里面,最终标记阶段需要把 Remembered Set Logs 的数据合并到 Remembered Set 中。这阶段需要停顿线程(STW),但是可并行执行。

**筛选回收:**首先对各个 Region 中的回收价值和成本进行排序,根据用户所期望的 GC 停顿时间来制定回收计划。此阶段其实也可以做到与用户程序一起并发执行,但是因为只回收一部分 Region,时间是用户可控制的,而且停顿用户线程将大幅度提高收集效率。

JVM七种垃圾回收器_垃圾回收器_10

5.分区概念

JVM七种垃圾回收器_垃圾回收_11

分区 Region

G1采用了分区(Region)的思路,将整个堆空间分成若干个大小相等的内存区域,每次分配对象空间将逐段地使用内存。因此,在堆的使用上,G1并不要求对象的存储一定是物理上连续的,只要逻辑上连续即可;每个分区也不会确定地为某个代服务,可以按需在年轻代和老年代之间切换。启动时可以通过参数-XX:G1HeapRegionSize=n可指定分区大小(1MB~32MB,且必须是2的幂),默认将整堆划分为2048个分区。

卡片 Card

在每个分区内部又被分成了若干个大小为512 Byte卡片(Card),标识堆内存最小可用粒度所有分区的卡片将会记录在全局卡片表(Global Card Table)中,分配的对象会占用物理上连续的若干个卡片,当查找对分区内对象的引用时便可通过记录卡片来查找该引用对象(见RSet)。每次对内存的回收,都是对指定分区的卡片进行处理。

堆 Heap

G1同样可以通过-Xms/-Xmx来指定堆空间大小。当发生年轻代收集或混合收集时,通过计算GC与应用的耗费时间比,自动调整堆空间大小。如果GC频率太高,则通过增加堆尺寸,来减少GC频率,相应地GC占用的时间也随之降低;目标参数-XX:GCTimeRatio即为GC与应用的耗费时间比,G1默认为9,而CMS默认为99,因为CMS的设计原则是耗费在GC上的时间尽可能的少。另外,当空间不足,如对象空间分配或转移失败时,G1会首先尝试增加堆空间,如果扩容失败,则发起担保的Full GC。Full GC后,堆尺寸计算结果也会调整堆空间。

6.G1 GC主要的参数

  1. 暂停时间:用-XX:MaxGCPauseMillis来指定,默认值200ms。这是一个软性目标,G1会尽量达成,如果达不成,会逐渐做自我调整。对于Young GC来说,会逐渐减少Eden区个数,减少Eden空间那么Young GC的处理时间就会相应减少;对于Mixed GC,G1会调整每次Choose Cset的比例,默认最大值是10%,当然每次选择的Cset少了,所要经历的Mixed GC的次数会相应增加。同时减少Eden的总空间时,就会更加频繁的触发Young GC,也就是会加快Mixed GC的执行频率,因为Mixed GC是由Young GC触发的,或者说借机同时执行的。频繁GC会对对应用的吞吐量造成影响,每次Mixed GC回收时间太短,回收的垃圾量太少,可能最后GC的垃圾清理速度赶不上应用产生的速度,那么可能会造成串行的Full GC,这是要极力避免的。所以暂停时间肯定不是设置的越小越好,当然也不能设置的偏大,转而指望G1自己会尽快的处理,这样可能会导致一次全部并发标记后触发的Mixed GC次数变少,但每次的时间变长,STW时间变长,对应用的影响更加明显。
  2. Region大小:用-XX:G1HeapRegionSize来指定,若未指定则默认最多生成2048块,每块的大小需要为2的幂次方,如1,2,4,8,16,32,最大值为32M。Region的大小主要是关系到Humongous Object的判定,当一个对象超过Region大小的一半时,则为巨型对象,那么其会至少独占一个Region,如果一个放不下,会占用连续的多个Region。当一个Humongous Region放入了一个巨型对象,可能还有不少剩余空间,但是不能用于存放其他对象,这些空间就浪费了。所以如果应用里有很多大小差不多的巨型对象,可以适当调整Region的大小,尽量让他们以普通对象的形式分配,合理利用Region空间。
  3. 新生代比例:新生代比例有两个数值指定,下限:-XX:G1NewSizePercent,默认值5%,上限:-XX:G1MaxNewSizePercent,默认值60%。G1会根据实际的GC情况(主要是暂停时间)来动态的调整新生代的大小,主要是Eden Region的个数。最好是Eden的空间大一点,毕竟Young GC的频率更大,大的Eden空间能够降低Young GC的发生次数。但是Mixed GC是伴随着Young GC一起的,如果暂停时间短,那么需要更加频繁的Young GC,同时也需要平衡好Mixed GC中新生代和老年代的Region,因为新生代的所有Region都会被回收,如果Eden很大,那么留给老年代回收空间就不多了,最后可能会导致Full GC。
  4. 并发GC线程数:通过 -XX:ConcGCThreads来指定,默认是-XX:ParallelGCThreads/4,也就是在非STW期间的GC工作线程数,当然其他的线程很多工作在应用上。当并发周期时间过长时,可以尝试调大GC工作线程数,但是这也意味着此期间应用所占的线程数减少,会对吞吐量有一定影响。

5.并行GC线程数:通过 -XX:ParallelGCThreads来指定,也就是在STW阶段工作的GC线程数,其值遵循以下原则:
① 如果用户显示指定了ParallelGCThreads,则使用用户指定的值。
② 否则,需要根据实际的CPU所能够支持的线程数来计算ParallelGCThreads的值,计算方法见步骤③和步骤④。
③ 如果物理CPU所能够支持线程数小于8,则ParallelGCThreads的值为CPU所支持的线程数。这里的阀值为8,是因为JVM中调用nof_parallel_worker_threads接口所传入的switch_pt的值均为8。
④ 如果物理CPU所能够支持线程数大于8,则ParallelGCThreads的值为8加上一个调整值,调整值的计算方式为:物理CPU所支持的线程数减去8所得值的5/8或者5/16,JVM会根据实际的情况来选择具体是乘以5/8还是5/16。
比如,在64线程的x86 CPU上,如果用户未指定ParallelGCThreads的值,则默认的计算方式为:ParallelGCThreads = 8 + (64 - 8) * (5/8) = 8 + 35 = 43。

  1. 被纳入Cset的Region的存活空间占比阈值:通过 -XX:G1MixedGCLiveThresholdPercent指定,不同版本默认值不同,有65%和85%。在全局并发标记阶段,如果一个Region的存活对象的空间占比低于此值,则会被纳入Cset。此值直接影响到Mixed GC选择回收的区域,当发现GC时间较长时,可以尝试调低此阈值,尽量优先选择回收垃圾占比高的Region,但此举也可能导致垃圾回收的不够彻底,最终触发Full GC。
  2. 触发全局并发标记的老年代使用占比:通过-XX:InitiatingHeapOccupancyPercent指定,默认值45%,也就是老年代占堆的比例超过45%。如果Mixed GC周期结束后老年代使用率还是超过45%,那么会再次触发全局并发标记过程,这样就会导致频繁的老年代GC,影响应用吞吐量。同时老年代空间不大,Mixed GC回收的空间肯定是偏少的。可以适当调高IHOP的值,当然如果此值太高,很容易导致年轻代晋升失败而出发Full GC,所以需要多次调整测试。
  3. 触发Mixed GC的堆垃圾占比:通过-XX:G1HeapWastePercent指定,默认值5%,也就是在全局标记结束后能够统计出所有Cset内可被回收的垃圾占整对的比例值,如果超过5%,那么就会触发之后的多轮Mixed GC,如果不超过,那么会在之后的某次Young GC中重新执行全局并发标记。可以尝试适当的调高此阈值,能够适当的降低Mixed GC的频率。
  4. 每轮Mixed GC回收的Region最大比例:通过-XX:G1OldCSetRegionThresholdPercent指定,默认10%,也就是每轮Mixed GC附加的Cset的Region不超过全部Region的10%,最多10%,如果暂停时间短,那么可能会少于10%。一般这个值不需要额外调整。
  5. 一个周期内触发Mixed GC最大次数:通过-XX:G1MixedGCCountTarget指定,默认值8。也就是在一次全局并发标记后,最多接着8此Mixed GC,也就是会把全局并发标记阶段生成的Cset里的Region拆分为最多8部分,然后在每轮Mixed GC里收集一部分。这个值要和上一个参数配合使用,8*10%=80%,应该来说会大于每次标记阶段的Cset集合了。一般此参数也不需额外调整。

11.G1为分配担保预留的空间比例:通过-XX:G1ReservePercent指定,默认10%。也就是老年代会预留10%的空间来给新生代的对象晋升,如果经常发生新生代晋升失败而导致Full GC,那么可以适当调高此阈值。但是调高此值同时也意味着降低了老年代的实际可用空间。
谨慎使用Soft Reference。如果SoftReference过多,会有频繁的老年代收集。-XX:SoftRefLRUPolicyMSPerMB参数,可以指定每兆堆空闲空间的软引用的存活时间,默认值是1000,也就是1秒。可以调低这个参数来触发更早的回收软引用。如果调高的话会有更多的存活数据,可能在GC后堆占用空间比会增加。 对于软引用,还是建议尽量少用,会增加存活数据量,增加GC的处理时间。

12.晋升年龄阈值:通过-XX:MaxTenuringThreshold指定,默认值15。一般新生对象经过15次Young GC会晋升到老年代,巨型对象会直接分配在老年代,同时在Young GC时,如果相同age的对象占Survivors空间的比例超过 -XX:TargetSurvivorRatio的值(默认50%),则会自动将此次晋升年龄阈值设置为此age的值,所有年龄超过此值的对象都会被晋升到老年代,此举可能会导致老年代需要不少空间应对此种晋升。一般这个值不需要额外调整。

(七)垃圾回收器的重要参数(使用-XX:)

参数

描述

UseSerialGC

虚拟机运行在Client模式下的默认值,打开此开关后,使用 Serial+Serial Old 的收集器组合进行内存回收

UseParNewGC

打开此开关后,使用 ParNew + Serial Old 的收集器组合进行内存回收

UseConcMarkSweepGC

打开此开关后,使用 ParNew + CMS + Serial Old 的收集器组合进行内存回收。Serial Old 收集器将作为 CMS 收集器出现 Concurrent Mode Failure 失败后的后备收集器使用

UseParallelGC

虚拟机运行在 Server 模式下的默认值,打开此开关后,使用 Parallel Scavenge + Serial Old(PS MarkSweep) 的收集器组合进行内存回收

UseParallelOldGC

打开此开关后,使用 Parallel Scavenge + Parallel Old 的收集器组合进行内存回收

SurvivorRatio

新生代中 Eden 区域与 Survivor 区域的容量比值,默认为8,代表 Eden : Survivor = 8 : 1

PretenureSizeThreshold

直接晋升到老年代的对象大小,设置这个参数后,大于这个参数的对象将直接在老年代分配

MaxTenuringThreshold

晋升到老年代的对象年龄,每个对象在坚持过一次 Minor GC 之后,年龄就增加1,当超过这个参数值时就进入老年代

UseAdaptiveSizePolicy

动态调整 Java 堆中各个区域的大小以及进入老年代的年龄

HandlePromotionFailure

是否允许分配担保失败,即老年代的剩余空间不足以应付新生代的整个 Eden 和 Survivor 区的所有对象都存活的极端情况

ParallelGCThreads

设置并行GC时进行内存回收的线程数

GCTimeRatio

GC 时间占总时间的比率,默认值为99,即允许 1% 的GC时间,仅在使用 Parallel Scavenge 收集器生效

MaxGCPauseMillis

设置 GC 的最大停顿时间,仅在使用 Parallel Scavenge 收集器时生效

CMSInitiatingOccupancyFraction

设置 CMS 收集器在老年代空间被使用多少后触发垃圾收集,默认值为 68%,仅在使用 CMS 收集器时生效

UseCMSCompactAtFullCollection

设置 CMS 收集器在完成垃圾收集后是否要进行一次内存碎片整理,仅在使用 CMS 收集器时生效

CMSFullGCsBeforeCompaction

设置 CMS 收集器在进行若干次垃圾收集后再启动一次内存碎片整理,仅在使用 CMS 收集器时生效

(八)简单的垃圾回收器工作示意图

JVM七种垃圾回收器_垃圾回收器_12

单线程收集就是新生代是一个线程收集,老年代是一个线程收集.
使用并行收集的话,多个线程可以同时启动垃圾回收器
简单的垃圾回收器在每进行一次回收的时候,它会暂停所有的用户线程.等待所有的垃圾回收器完成工作,用户线程才能继续工作.
这就是为什么不能在代码里面写System.GC()的原因了,因为进行垃圾回收的时候所有的用户线程都会暂停.


举报

相关推荐

0 条评论