JVM性能——垃圾回收器的介绍

14 篇文章 1 订阅

JVM性能——垃圾回收器的介绍

JVM中的垃圾回收过程是通过查找那些不使用的对象,然后回收这些对象使用的内存的过程。GC保证了JVM的正常运行,但是GC的过程会导致JVM所有线程都停止运行的停顿,STW(stop-the-world pause)停顿对应用的性能产生很大的影响,GC的优化的目的就是来减少这部分停顿。

JDK版本:OpenJDK 1.8.0_352-b08

操作系统:CentOS 7

如果文章内出现测试数据测试代码

depth:23

关于JVM的其他文章

JVM性能——垃圾回收器的优化策略

JVM性能——垃圾回收器的介绍

JVM性能——开启回收日志和实时查看GC信息

JVM性能——JVM调优参数列表

GC需要了解的术语

Minor GC:也叫Young GC或者YGC,一般指的是Eden区满的时候触发的对新生代的回收动作。

Full GC:也可以称为Major GC ,主要是对整个堆空间的清理。触发的原因有很多老年代不足、大对象直接分配等等。对于回收器很多时候Full GC 是一个单线程且很重的行为,会给应用服务带来明显的影响,所以要尽量避免Full GC

STW:top Tow World的缩写,指的是虚拟机停顿正在执行的应用线程的动作。

并发收集周期:对于CMS和G1回收器,在应用系统运行的过程中,回收器会并发的执行一些回收的标记动作,最后执行会产生STW的回收动作

分代垃圾回收器的堆内存划分

垃圾回收器的时候会通过分代来实现对堆回收的不同逻辑,即使是后来添加的G1也是有分代的设计的,介绍堆内存的划分是没办法脱离垃圾回收器的。对于分代垃圾回收器来说堆内存的定义大体上是相同的。但是在后续出现的实验性GC工具已经抛弃了分代的设计。

新生代和老年代

垃圾回收器将堆分为两部分,新生代老年代

新生代

新生代包含三个空间。对象最开始会创建在Eden空间。当Eden空间满了之后触发YGC,存活的对象会被复制到Survivor空间中。Survivor空间存在2个,jstat命令中会标记为S0、S1,对象在两个空间中来回移动,一个空间保存上次GC后存活且没有晋升老年代的对象,另外一个空间保持空的。每次移动双方索引发生变化,所以我们总是看到只有S1在被使用

这么设计的目的

要想理解垃圾回收器为什么要设计这么多空间,就需要了解实际使用中对象的生命周期。

我们实际使用中创建的对象大多数使用后很快就不在使用了,但是也会有一些使用时间较长的对象,如果将这两种对象都放在一起进行扫描,如果使用较快的频率扫描这些长久存活的对象就会被反复处理,而如果使用较慢频率扫描这些寿命短的对象则会长时间占据内存。并且GC每次都对整个堆进行扫描也会延长了扫描的时间,导致更长的STW。而通过将堆区分为新老区域,可以针对预期存活时间不同的对象使用不同的GC频率,并且避免了对堆进行整体扫描,这样我们可以保证GC的效率并尽可能减少STW的时间。

GC的算法

下面列出了JDK8和JDK11版本存在的GC算法

GC算法JDK8JDK11
Serial支持支持
Parallel支持支持
G1 GC支持支持
CMS支持废弃
ZGC不支持实验
Shenandoah实验(openJDK)实验(openJDK)
Epsilon GC不支持实验

分代垃圾回收器

这里不在介绍那些特殊指定新生代或老年代的GC配置,如ParNewGC等。主要介绍SerialGCParallelGCConcMarkSweepGCG1GC

不同的GC设置使用的回收器名字

垃圾回收器新生代老年代
-XX:+UseSerialGCDefNewSerialOld
-XX:+UseParallelGCParallel ScavengeParallelOld
CMSParNewConcurrentMarkSweep
G1GCG1G1

Serial

Serial 收集器是最基本、历史最悠久的垃圾收集器了。是一个串行GC算法。使用此垃圾回收器后其新生代使用mark-copy(标记-复制) 算法进行回收。其老年代使用 mark-sweep-compact(标记-清除-整理)算法。正常发展看起来此算法在未来将会被淘汰,但是随着容器化部署的出现,在某些容器化场景下其存在使用的可能。

使用此回收器

可以使用此命令开启此收集器,但是在32位的JVM和单处理器的机器上,默认使用此回收器

-XX:+UserSerialGC
垃圾回收流程

因为是串行回收器,所以在进行任何GC动作的时候都会进行STW

新生代:使用复制算法进行回收

新生代的GC日志大概是这样的:

19.654: [GC (Allocation Failure) --[PSYoungGen: 288768K->288768K(288768K)] 864224K->988152K(988160K), 0.2778604 secs] [Times: user=0.44 sys=0.03, real=0.28 secs] 

老年代:使用标记-整理算法进行回收

老年代的GC日志大概是这样的:

19.932: [Full GC (Ergonomics) [PSYoungGen: 288768K->0K(288768K)] [ParOldGen: 699384K->587020K(699392K)] 988152K->587020K(988160K), [Metaspace: 3177K->3177K(1056768K)], 1.2328763 secs] [Times: user=2.39 sys=0.00, real=1.23 secs] 

Serial回收器因为进行单线程回收,所以相比其他收集器,通过增加服务器核心数并不能降低明显的降低GC总时间。但是相比其他其他回收器,在较低配置单核心限制或者服务器上CPU资源紧张的环境上时表现的比其他收集器效果要好。在1G的堆内存分配下跑测试代码出来的结果

在这里插入图片描述

Parallel垃圾回收器

Parallel被称为吞吐量优先收集器,使用多个线程回收新生代和老年代,因为使用多线程,所以此回收器也成为并行回收器。同样在进行任何GC的时候都会停止所有的应用程序,在Full GC的时候会压缩老年代。

使用此回收器

可以使用此命令开启此收集器,在64位的JVM或多处理器的机器上,默认使用此回收器

-XX:+UseParallelGC

新生代ParallelScavenge:它是基于标记 -复制算法实现的收集器,在收集过程中进行多线程收集。和下面介绍的CMS不同,相比缩短回收时用户线程停止的时间,它更关注吞吐量,降低GC在应用运行时间的占比

老年代ParallelOld:是ParallelScavenge的老年代版本

相比其他的回收器Parallel更关注的是GC占用程序运行时间的比例,下面是在4核2G堆内存跑出来的结果

在这里插入图片描述

垃圾回收流程

在Eden空间填满后,新生代GC就会发生。YGC会将所有的对象移出Eden空间,部分对象会被移入其中一个Survivor空间中,另外一些会被移动到老年代中,然后对堆进行压缩。YGC后Eden空间会成为空的内容。而老年代的回收基本上是一样的逻辑

10.255: [GC (Allocation Failure) [PSYoungGen: 347232K->96K(348160K)] 740729K->393593K(1047552K), 0.0009833 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
19.677: [Full GC (Ergonomics) [PSYoungGen: 94208K->0K(210944K)] [ParOldGen: 659529K->549815K(699392K)] 753737K->549815K(910336K), [Metaspace: 3178K->3178K(1056768K)], 1.2134059 secs] [Times: user=2.34 sys=0.00, real=1.21 secs] 

CMS

CMS存在一个并发周期,并发周期和应用线程同时进行,这样相对与其他的收集器,STW的时间会有所减少,但是因为存在和应用线程CPU的争抢,系统的吞吐量会降低。尽量减少由于主要收集而导致的暂停时间。但是它使用的标记清除算法也会产生大量空间碎片,在存在大量对象从老年代中淘汰的场景中,因为碎片的存在,导致出现空间足够但是却没有连续的空间保证新生代对象晋升或者大对象的分配进而进行Full GC。加上后续出现更加优秀的G1回收器导致其最终被废弃。它的主要适合场景是对响应时间的重要性需求大于对吞吐量的要求,能够承受垃圾回收线程和应用线程共享处理器资源,并且应用中存在比较多的长生命周期的对象的应用。

使用此回收器

使用此命令开启CMS垃圾回收器

-XX:+UseConcMarkSweepGC

使用此回收器后,老年代使用并发标记清除,新生代使用ParNew收集算法

ParNew:ParNew收集器是Serial收集器的多线程版本,采用并行回收的方式,回收时会进行STW,除了Serial收集器,目前只有它能与CMS收集器配合工作。

ConcurrentMarkSweep回收流程

CMS会根据堆的占用情况(这个比例可以使用CMSInitiatingOccupancyFraction调整)启动并发周期。虽然是并发周期,但是实际上在某些阶段还是会暂停服务(STW),但是会短很多

初始标记

在初始阶段会暂停所有应用线程(STW)。在这个阶段主要工作是下面两部分。

  1. 遍历GCRoot可直达的直接关联的对象
  2. 遍历新生代直达的老年代的对象

如果开启了GC日志可以看到日志中下面的提示

16.582: [GC (CMS Initial Mark) [1 CMS-initial-mark: 442261K(878208K)] 467464K(1031552K), 0.0134611 secs] [Times: user=0.03 sys=0.00, real=0.01 secs] 
并发标记

这个阶段耗时较长但是不需要停顿用户线程,主要是进行标记,标记的内容主要有下面内容:

  1. 根据上一阶段标记对象开始遍历整个对象的过程,标记存活的对象
  2. 会有部分新生代晋升到老年代,直接在老年代分配的对象。这些对象所在的card table被标记为dirty

在GC日志中可以看到下面的内容,在开始和结束中间会穿插比较多的GC日志

16.596: [CMS-concurrent-mark-start]
......
17.389: [GC (Allocation Failure) 17.389: [ParNew: 153186K->13116K(153344K), 0.0020644 secs] 684798K->544728K(1031552K), 0.0021071 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
17.695: [CMS-concurrent-mark: 0.620/1.099 secs] [Times: user=2.13 sys=0.01, real=1.10 secs] 
预清理

此阶段与应用线程并发执行的,不需要停止应用线程。

主要内容是标记老年代新增的对象,重新标记那些在并发标记阶段新增加和引用被更新的对象。

在GC日志中可以看到下面的内容

17.695: [CMS-concurrent-preclean-start]
17.696: [CMS-concurrent-preclean: 0.001/0.001 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
可中断的预清理

这个阶段可能因为YGC而中断。这里的预清理被设置为可中断的主要是基于暂停时间的考虑。下一阶段的最终标记会进行STW,如果在新生代回收后立刻进入最后阶段这时会导致连续出现两个较长的STW。会扩大应用线程的暂停时间。CMS会根据之前YGC的间隔预估下一次YGC的时间,在两次YGC的中间时间进入下一阶段。所以在预清理阶段需要可以被中止。

在GC日志中可以看到下面的内容

17.696: [CMS-concurrent-abortable-preclean-start]
17.707: [GC (Allocation Failure) 17.707: [ParNew: 152689K->17024K(153344K), 0.0793232 secs] 716641K->607300K(1031552K), 0.0793972 secs] [Times: user=0.15 sys=0.00, real=0.08 secs] 
17.843: [GC (Allocation Failure) 17.843: [ParNew: 153344K->17024K(153344K), 0.0544503 secs] 743620K->622486K(1031552K), 0.0545129 secs] [Times: user=0.09 sys=0.00, real=0.05 secs] 
17.922: [CMS-concurrent-abortable-preclean: 0.091/0.225 secs] [Times: user=0.42 sys=0.00, real=0.23 secs] 
最终标记

此阶段会进行服务暂停(STW)。CMS使用了三色标记法,在这一刻要对所有标记内容进行最终标记。之前应用线程还在执行,存在应用线程有可能改变了对象间的引用关系。所以这个阶段需要暂停服务线程,进行最终的标记,避免出现遗漏。

对应GC日志中的提示

17.926: [GC (CMS Final Remark) [YG occupancy: 85183 K (153344 K)]17.926: [Rescan (parallel) , 0.0394236 secs]17.966: [weak refs processing, 0.0000087 secs]17.966: [class unloading, 0.0001540 secs]17.966: [scrub symbol table, 0.0002801 secs]17.966: [scrub string table, 0.0000508 secs][1 CMS-remark: 605462K(878208K)] 690646K(1031552K), 0.0399904 secs] [Times: user=0.08 sys=0.00, real=0.04 secs] 

清除阶段

这个阶段是并发的,不需要进行STW,上一阶段标记了本次 GC 的内容。因为和应用线程并发执行会有新的垃圾不断产生,这一部分垃圾出现在标记过程之后,CMS无法在当次收集中处理掉它们,只好留待下一次GC时再清理掉。

对应GC日志中的提示

17.966: [CMS-concurrent-sweep-start]
17.986: [GC (Allocation Failure) 17.986: [ParNew: 153344K->17024K(153344K), 0.0234928 secs] 758806K->626535K(1031552K), 0.0235572 secs] [Times: user=0.05 sys=0.00, real=0.03 secs] 
18.047: [GC (Allocation Failure) 18.047: [ParNew: 153344K->16842K(153344K), 0.0115139 secs] 762855K->626353K(1031552K), 0.0115684 secs] [Times: user=0.02 sys=0.00, real=0.02 secs] 
18.090: [GC (Allocation Failure) 18.090: [ParNew: 153162K->17024K(153344K), 0.0787338 secs] 762673K->657461K(1031552K), 0.0788110 secs] [Times: user=0.16 sys=0.00, real=0.08 secs] 
18.217: [GC (Allocation Failure) 18.217: [ParNew: 153344K->17024K(153344K), 0.0588078 secs] 793781K->677250K(1031552K), 0.0588731 secs] [Times: user=0.12 sys=0.00, real=0.05 secs] 
18.336: [GC (Allocation Failure) 18.336: [ParNew: 153344K->17024K(153344K), 0.0447392 secs] 813570K->685902K(1031552K), 0.0447980 secs] [Times: user=0.07 sys=0.00, real=0.05 secs] 
18.436: [GC (Allocation Failure) 18.436: [ParNew: 153344K->16881K(153344K), 0.0154004 secs] 822222K->685760K(1031552K), 0.0154506 secs] [Times: user=0.03 sys=0.00, real=0.02 secs] 
18.465: [CMS-concurrent-sweep: 0.262/0.499 secs] [Times: user=0.94 sys=0.01, real=0.50 secs] 
并发重置

这个阶段就是,重新设置CMS算法内部的数据结构,准备下一个CMS生命周期的使用。

18.465: [CMS-concurrent-reset-start]
18.466: [CMS-concurrent-reset: 0.001/0.001 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
CMS回收器的问题

失败的并发模式

在进行新生代回收的时候,老年代没有足够的对象进行分配的时候,CMS会进行Full GC这个过程会导致程序长时间暂停这个动作是单线程的会让应用长时间暂停,当有大量对象晋升到老年代的时候问题会更加突出。

下面的日志中,一次Full GC就用去了1.92秒

22.783: [GC (Allocation Failure) 22.783: 
[ParNew: 153344K->153344K(153344K), 0.0000122 secs]22.783: 
[CMS23.375: [CMS-concurrent-mark: 0.763/1.140 secs] 
[Times: user=1.66 sys=0.00, real=1.14 secs] 
 (concurrent mode failure): 760133K->511680K(878208K), 1.9227898 secs] 913477K->511680K(1031552K), 
 [Metaspace: 3177K->3177K(1056768K)], 1.9228640 secs] [Times: user=1.81 sys=0.11, real=1.92 secs] 

相比其他回收器CMS更在意CPU资源,相比ParallelG1更在意CPU资源,在低CPU的场景下CMS回收时间如此夸张。这是因为Parallel在回收的时候会进行STW,这样就阻止新生代的晋升。而G1因为将堆划分更小的区域,其对新生代的比例上限也更高可以让其在压力大的时候将新生代增加减少触发Full GC的可能。但是CMS在进行回收的时候应用线程依然在进行新生代的晋升,如果回收完成前老年代无法分配新的晋升对象将会出现Full GC,直接导致GC时间变得非常长。但是伴随增加CPU核心数,CMS的GC时间会大幅度下降。

在这里插入图片描述

碎片化的空间

CMS的清理方式基于标记-清除算法的,只会将标记为为存活的对象删除,并不会移动对象整理内存空间。这样会使老年代并不是连贯的,碎片化的堆在分配对象的时候会出现空间满足但是却无法分配对象的情况,这个时候CMS必须停止所有的应用线程并压缩堆,这就导致会有长时间的服务暂停。

G1 垃圾回收器

虽然G1依旧回去划分新生代和老年代,但是它并不是将新生代和老年代认为是一个单独的区域。G1回收器将堆分成多个区域(Region),每个区域会被G1标记为新生代老年代,这么每个区域的GC对应用整体影响会变得很小,同时因为扫描区域的缩短,STW的时间也会变得很短。作为JVM GC算法的一次重大升级、后续版本已经替代CMS。

使用此回收器

可以使用此命令开启此收集器,在JDK11之后此回收器为默认回收器,在JDK8版本中也可以使用,但是推荐使用JDK8的后期版本,在后期版本中移植了很多重要的BUG修复和增强

-XX:UseG1GC
Mixed GC

G1并没有严格意义上的分区,它将内存划分一个一个的区域(Region)。这些区域在某个时刻会被认定为Eden或者S区或者Old区。但是每个区域不是固定的,随着运行情况的变化,区域归属的职责也是变化的。

G1使用Mixed GC(混合回收)进行垃圾清理。之所以叫混合回收是因为除了会进行新生代的回收,还会回收后台扫描时标记的部分区域(包括老年代)。Mixed GC 周期会执行多次直到所有标记区域都被完成回收。G1 GC在完成对标记区域回收后,会将这些区域的对象移动到另外的区域中,G1使用这么方式对老年代进行压缩。G1的回收流程可以分下面的步骤:

初始标记

这个阶段会暂停应用线程(STW)。主要做的操作是。

  1. 标记了从GC Root开始直接关联可达的对象
  2. 进行了新生代的回收,这也是导致STW的原因

在GC日志中也可以看到在完成标记周期后Eden空间大小从321M调整到了321M并且清空了内存占用,Survivors空间大小从52224.0K调整到了13312.0K。

16.422: [GC pause (G1 Evacuation Pause) (young) (initial-mark), 0.0165349 secs]
   ......
   [Eden: 321.0M(321.0M)->0.0B(372.0M) Survivors: 52224.0K->13312.0K Heap: 841.2M(1024.0M)->482.2M(1024.0M)]
 [Times: user=0.03 sys=0.00, real=0.02 secs] 
根区域扫描

这个阶段不需要暂停应用线程(STW),主要进行的操作是。

  1. 在上一阶段完成YGC后,新的对象被复制到Survivor。为了保证标记算法的正确性,所有新复制到 Survivor 分区的对象,都需要被扫描并标记成根,根分区扫描必须在下一次年轻代垃圾收集启动前完成,所以这个阶段不能被新生代的回收所暂停。
0.805: [GC concurrent-root-region-scan-start]
0.812: [GC concurrent-root-region-scan-end, 0.0070055 secs]
并发标记

这个阶段不需要暂停应用线程(STW),但是这个阶段的操作会被新生代回收所中断。这个阶段会沿着上个阶段标记到的根对象。所有的标记任务必须在堆满前就完成扫描,否则经历一次长时间的串行 Full GC。 如果并发标记耗时很长,那么有可能在并发标记过程中进行几次年轻代收集。

0.812: [GC concurrent-mark-start]
......
1.230: [GC concurrent-mark-end, 0.4175104 secs]
重新标记(最终标记)

这个阶段会暂停应用线程(STW),主要操作是。重新标记并发阶段结束后仍遗留下来的最后那少量的SATB记录和所有更新。同时安全完成存活数据计算。这个阶段也是并行执行的,通过参数 -XX:ParallelGCThread 可设置 GC 暂停时可用的 GC 线程数。

16.257: [GC remark 16.257: [Finalize Marking, 0.0003073 secs] 16.257: [GC ref-proc, 0.0001121 secs] 16.257: [Unloading, 0.0006524 secs], 0.0022019 secs]
 [Times: user=0.00 sys=0.00, real=0.01 secs] 
清理阶段

这个阶段会暂停应用线程(STW),主要操作是。

  1. 识别出所有空闲的分区,每个region的垃圾比率进行排序,优先清理垃圾最多的区域。
  2. 整理堆分区,为混合收集周期识别回收收益高的老年代分区集合。
  3. 识别所有空闲分区,即发现无存活对象的分区。该分区可在清除阶段直接回收

虽然这一阶段叫做清理阶段,但是实际上并没有进行真正的清理动作。

1.231: [GC cleanup 896M->524M(1024M), 0.0007274 secs]
 [Times: user=0.00 sys=0.00, real=0.00 secs] 
1.232: [GC concurrent-cleanup-start]
1.232: [GC concurrent-cleanup-end, 0.0002230 secs]
混合回收周期

截至到上一阶段并发标记周期已经结束。这个时候G1已经获得需要清理的区域清单。在之后 G1 并不会马上开始一次混合收集,而是等待触发一次年轻代收集。在这次 STW 中,首次触发Mixed GC会在日志中将其标记,然后接下来的几次年轻代收集时,将标记的老年代分区加入到 CSet 中,进行混合清理。

并发失败

因为是并发操作,在实际运行中如果并发周期启动的过晚或者CPU资源不足会导致Mixed GC回收空间的速度很慢,而对象增长的很快,这导致无法分配晋升对象,那么就会触发Full GC。如果出现了Full GC则需要考虑是否增加堆的大小,或者优化标记周期使其更快的运行。

9.886: [Full GC (Allocation Failure)  511M->289M(512M), 0.7627054 secs]
   [Eden: 0.0B(25.0M)->0.0B(96.0M) Survivors: 0.0B->0.0B Heap: 511.5M(512.0M)->289.4M(512.0M)], [Metaspace: 2663K->2663K(1056768K)]
 [Times: user=0.86 sys=0.02, real=0.76 secs] 
10.649: [GC concurrent-mark-abort]

G1的通过G1MaxNewSizePercent(默认值新生代的占比不会超过60%)使得新生代可以使用的内存远远大于CMS,所以在某些时候,可以通过扩大新生代来减少YGC的频率进而给Mixed GC提供更多的宽容度

实验性GC

实验性的GC包含三个:Shenandoah GCZ GCEpsilon GC其中Shenandoah GC被移植到了JDK8中。

使用实验性GC需要设定参数

-XX:+UnlockExperimentalVMOptions

实验性的GC带来的两个影响:

  1. 堆不需要再分代了。之前GC之所以区分新老区域,是为了在回收的时候使用不同的策略,来进了压缩GC时服务暂停的时间。如果回收期间服务不再需要暂停了,那么之前之所以做那么多区分的意义就不存在了,也就不需要分代了。
  2. 应用操作可能的延迟减少。再之前的GC工具中,因为存在服务暂停,对于一个我们预计一定时间可以完成的操作,不得不考虑GC带来的影响。通常GC带来的服务暂停包含短时间的YGC和长时间的FGC。这样导致我们预估操作时间的范围会很大。

Shenandoah GC

ShenandoahGC收集器,它与G1、ZGC收集器一样。它们的停顿时间都不会受到堆空间大小的影响。它也会将堆内存划分为一个个 大小相同的Region区域,但是并不会进行分代。追求极致低延迟。

使用此GC
-XX:+UseShenandoahGC

Shenandoah GC的平均时间

不得不说GC平均暂停时间的确控制的很低,即使在低资源的情况下每次暂停时间也控制在10ms左右。

在这里插入图片描述

Z GC

在JDK11中,Z GC被以实验性的特性引入,在JDK15中正式投入使用。在JDK16发布后,GC暂停时间已经缩小到1ms以内,并且时间复杂度为o(1)了,这也意味着GC停顿时间是一个固定值,不会受到堆内存大小的影响了。

使用此GC
-XX:+UseZGC

Epsilon GC

一个不进行垃圾回收的垃圾回收算法,在堆内存满后会抛出内存溢出的提示。其主要为了JDK内部测试使用。因为不涉及垃圾回收,所以其分配内存的速度有了很大的提高。我们可以做一些可以预测资源使用的测试场景

使用此GC
-XX:+UseEpsilonGC

如何选择GC算法

如果你是容器化部署且资源非常紧张的情况下可以尝试使用Serial。而其他的情况GC的选择就要结合硬件环境和性能目标来结合考虑。

其他情况的选择

选择回收器时需要思考,我是更关注程序的响应时间还是更关注CPU的利用率。以及机器中CPU的资源是否充足。是否可以分配足够的内存给JVM。

CPU资源比较紧张

在CPU资源比较紧张的时候,并且Full GC的频率较低的时候,使用Parallel会更好些,毕竟它对CPU的要求更低些。

CPU资源比较充裕

对于JDK8的用户,因为G1已经移植到当前版本。那么CMS已经没有使用的必要了。直接使用G1。在有足够的CPU提供给G1时,使用G1会是一个比较好的选择。即使产生大量Full GC的时候G1垃圾回收器的区域划分会获得更高的回收效率。前提只要有足够的CPU。

当然具体使用哪种回收器,最稳妥的办法时,确定使用目标后的一段时间内,在应用环境中监控服务的GC日志以及服务器压力等数据。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

大·风

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值