垃圾回收器的历史
:::warning
1999年:JDK1.3.1,Serial GC(第一款GC),ParNew GC是SerialGC的多线程版本。
2002年:JDK1.4.2,Parallel GC和Concurrent Mark Sweep GC(CMS)。Parallel GC在JDK6之后成为Hotspot默认GC。
2012年:JDK1.7u4,G1可用。
2017年:JDK9,G1成为默认垃圾回收器,以替代CMS.
2018年:JDK10,G1的并行完整垃圾回收,实现并行性能改善最坏情况的延迟。
2018年9月,JDK11,引入Epsilon GC,又称为”No-Op无操作”回收器,同时引入ZGC:可伸缩的低延迟回收器(Experimental)
2019年3月,JDK12,增加G1,自动返回未使用堆内存给操作系统;同时,引入Shenandoah GC:地停顿时间的GC(Experimental)
2019年9月,JDK13,增强ZGC,自动返回未使用堆内存给操作系统。
2020年3月,JDK14,删除CMS,扩展ZGC,在mac和windows的应用。
2020年9月,JDK15
2021年3月,JDK16
2021年9月,JDK17
2022年3月,JDK18
:::
垃圾回收器的组合关系
:::warning
- JDK 8中:将Serial+CMS、 ParNew+Serial Old这两个组合声明为废弃(JEP 173) ,并在JDK 9中完全移除了这些组合。
- JDK 14中:弃用Parallel Scavenge和SerialOld GC组合(JEP366 )
JDK 14中:删除CMS垃圾回收器 (JEP 363) :::
垃圾回收器默认CP
UseSerialGC 表示 “Serial” + “Serial Old”组合
- UseParNewGC 表示 “ParNew” + “Serial Old”
- UseConcMarkSweepGC 表示 “ParNew” + “CMS”. 组合
- UseParallelGC 表示 “Parallel Scavenge” + “Parallel Old”组合
- UseG1GC 表示使用G1
jdk1.8已标记废弃UseParNewGC
Java HotSpot(TM) 64-Bit Server VM warning: Using the ParNew young collector with the Serial old collector is deprecated and will likely be removed in a future release
JDK默认的垃圾回收器
频繁推出垃圾回收器的原因?
解决STW问题
:::info Stop the World机制:在执行垃圾收集算法时,为了保证正确性,Java应用程序的其他所有除了垃圾收集收集器线程之外的线程都被挂起,它会导致系统全局的停顿。 :::
垃圾回收器的分类
串行GC:采用单线程回收垃圾,用户线程处于等待状态,适合于堆内存空间比较小和个人小项目
并行GC:多条垃圾收集线程并行工作,但用户线程仍然处于等待状态。
并发GC:用户线程和垃圾收集线程同时执行,他们运行于不同的CPU上。(不一定是并行,可能是交替执行)
评估GC的性能指标
吞吐量、暂停时间、内存占用共同构成一个”不可能三角”。三者总体的表现会随着技术进步而越来越好。一款优秀的收集器通常最多同时满足其中的两项。这三项里,暂停时间的重要性日益凸显。因为随着硬件发展,内存占用多越来越能被容忍,硬件性能的提升也有助于降低收集器运行时对应用程序的影响,即提高了吞吐量。而内存的扩大,对延迟反而带来负面效果。
:::info 简单来说,主要抓住两点:吞吐量 + 暂停时间 :::
吞吐量
- 吞吐量就是CPU用于运行用户代码的时间与CPU总消耗时间的比值,即吞吐量=user时间/(user时间+gc时间)
- 比如:虚拟机总共运行了100分钟,其中垃圾收集花掉1分钟,那吞吐量就是99%
-
暂停时间
“暂停时间”是指一个时间段内应用程序线程暂停,让GC线程执行的状态。
- 例如,GC期间100毫秒的暂停时间意味着在这100毫秒期间内没有应用程序线程是活动的。
- 暂停时间优先,意味着尽可能让单次STW的时间最短。
高吞吐与低暂停取舍
- 高吞吐量较好因为这会让应用程序的最终用户感觉只有应用程序线程在做”生产性”工作。直觉上,吞吐量越高程序运行越快。
- 低暂停时间(低延迟)较好因为从最终用户的角度来看不管是GC还是其他原因导致一个应用被挂起始终是不好的。这取决于应用程序的类型,有时候甚至短暂的200毫秒暂停都可能打断终端用户体验。因此,具有低的暂停时间是非常重要的,特别是对于一个交互式应用程序。
- 不幸的是”高吞吐量”和”低暂停时间”是一对相互竞争的目标(矛盾)。
- 因为如果选择以吞吐量优先,那么必然需要降低内存回收的执行频率,但是这样会导致GC需要更长的暂停时间来执行内存回收。
- 相反的,如果选择以低延迟优先为原则,那么为了降低每次执行内存回收时的暂停时间,也只能频繁地执行内存回收,但这又导致程序吞吐量的下降。
-
CMS垃圾回收器(高吞吐)
文章推荐
从实际案例聊聊Java应用的GC优化 - 美团技术团队
Java中9种常见的CMS GC问题分析与解决 - 美团技术团队算法说明
Concurrent-Mark-Sweep,第一款并发垃圾回收器,关注点是尽可能缩短垃圾收集时用户线程的停顿时间。算法是标记清除 ,适用于老年代。配合新生代ParNew或者Serial收集器使用,并使用Serial Old作为后备垃圾收集方案。
回收步骤
[ ] 初始标记(STW):标记GC Roots直接关联到的对象。停顿短
- 并发标记(并行):进行GC Roots Tracing。停顿长。
- 重新标记(STW):修正并发标记期间,因用户程序继续运行而导致标记产生变动的那一部分对象。停顿较短。
- 并发清除(并行):停顿长。
缺点
- CMS收集器对CPU资源非常敏感。在并发阶段,虽然可以和用户线程并发执行,但是由于占用一部分CPU资源,可能导致用户线程突然变慢,总的吞吐量变变低。
- 无法处理浮动垃圾。在并发收集阶段,用户线程在运行过程中仍然会产生新的垃圾,而这些垃圾只能在下一次被标记回收。可能出现”Concurrent Mode Failure”而导致另一次Full GC的产生。
- 由于并发收集,要给用户线程预留足够的内存空间。所以,CMS不能等到老年代几乎被填满了再进行收集,需要预留一部分空间提供并发收集时的程序运行。可以通过参数
-XX:CMSInitiatingOccupancyFraction
设置在老年代被使用了多少的时候,触发回收。如果空间不足,会导致出现Concurrent Mode Failure,这时,将启动预案:临时启用Serial Old收集器重新进行老年代的垃圾回收。这样停顿时间就更长了。所以-XX:CMSInitiatingOccupancyFraction
不能设置的太高,JDK 1.6的默认值是92%。
- 由于并发收集,要给用户线程预留足够的内存空间。所以,CMS不能等到老年代几乎被填满了再进行收集,需要预留一部分空间提供并发收集时的程序运行。可以通过参数
- 空间碎片。因为用的是标记-清除算法。当碎片过多时,无法给大对象分配空间,从而提前触发Full GC。
- 设置
+UseCMSCompactAtFullCollection
,默认开启。这个参数控制CMS在空间不足时,进行内存碎片整理。会导致停顿时间变长。 - 设置
-XX:CMSFullGCsBeforeCompaction
,这个参数用于设置执行多少次不压缩的Full GC后,跟着执行一次带压缩的。默认是0,表示每次进入Full GC时都进行碎片整理。回收模式
CMS GC我们分为两种模式background和foreground
CMS的GC模式解读
顾名思义是在后台做的,也就是可以不影响正常的业务线程跑,触发条件比如说old的内存占比超过多少的时候就可能触发一次background式的cms gc,这个过程会经历cms gc的所有阶段,该暂停的暂停,该并行的并行,效率相对来说还比较高,毕竟有和业务线程并行的GC阶段;常规理解中的并发收集,可以不影响正常的业务线程运行。background
发生的场景:比如业务线程请求分配内存,但是内存不够了,于是可能触发一次cms gc,这个过程就必须是要等内存分配到了线程才能继续往下面走的,因此整个过程必须是STW的,因此cms gc整个过程都是暂停应用的。foreground
- 设置
但是为了提高效率,它并不是每个阶段都会走的,只走其中一些阶段,这些省下来的阶段主要是并行阶段。
会进行一次压缩式GC。此压缩式GC使用的是跟SerialOldGC一样的Lisp2算法,其使用Mark-Compact来做FullGC,一般称之为MSC(Mark-Sweep-Compact),它收集的范围是Java堆的Young区和Old区以及MetaSpace。
compact的代价是巨大的。那么使用Foreground Collector时将会带来非常长的STW。
G1 收集器(低暂停,可预测)
文章推荐
Java Hotspot G1 GC的一些关键技术 - 美团技术团队
G1-垃圾回收简述(三) | HeapDump性能社区
算法说明
- 充分利用多CPU、多线程的硬件优势,缩短STW的时间。
- 算法:从整体看是标记-整理算法,从局部(两个Region之间)是基于复制算法实现的。
- 分代的概念依然保留。
- 可预测的停顿时间。G1跟踪各个Region 里面的垃圾堆积的价值大小(回收所获得的空间大小以及回收所需时间的经验值),在后台维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的Region。
- 内存布局:使用G1收集器时,Java堆的内存布局是将整个Java堆划分为多个大小相等的独立区域(Region),新生代和老年代都是一部分Region的集合(不需要连续)。
G1参数设置
| 参数 | 含义 | | —- | —- | | -XX:G1HeapRegionSize=n | 设置Region大小。值是2的幂,范围是1MB 到32MB之间,目标是根据最小的Java堆大小划分出约2048个区域。默认是堆内存的1/2000。 | | -XX:G1NewSizePercent | 新生代最小值,默认值5% | | -XX:G1MaxNewSizePercent | 新生代最大值,默认值60% | | -XX:ParallelGCThreads | STW期间,并行GC线程数,最多设置为8。 | | -XX:ConcGCThreads=n | 设置并发标记的线程数,设置为并行垃圾回收线程数(ParallelGCThreads)的1/4左右。 | | -XX:InitiatingHeapOccupancyPercent | 设置触发标记周期的 Java 堆占用率阈值。超过此值,就触发Mixed GC,默认值是45%。这里的java堆占比指的是non_young_capacity_bytes,包括old+humongous | | -XX:+UseG1GC | 手动指定使用G1收集器执行内存回收任务。 | | -XX:MaxGCPauseMillis | 设置期望达到的最大GC停顿时间指标(JVM会尽力实现,但不保证达到)。默认值是200ms |
回收步骤
- 初始标记(STW)
- 并发标记(并行)
- 最终标记(STW)
- 筛选回收(STW)
回收过程
G1 GC的垃圾回收过程主要包括如下三个环节:
- 年轻代GC (Young GC )
- 老年代并发标记过程( Concurrent Marking)
- 混合回收(Mixed GC )
- Full GC(单线程、独占式、高强度的Full GC还是继续存在的。它针对GC的评估失败提供了一种失败保护机制,即强力回收。)
- 应用程序分配内存,当年轻代的Eden区用尽时开始年轻代回收过程; G1的年轻代收集阶段是一个并行的(多个回收线程)独占式(STW)收集器。在年轻代回收期,G1 GC暂停所有应用程序线程,启动多线程执行年轻代回收。然后从年轻代区间移动存活对象到Survivor区间或者老年区间,也有可能是两个区间都会涉及。
- 当堆内存使用达到一定值(默认45%)时,开始老年代并发标记过程。
- 标记完成马上开始混合回收过程。对于一个混合回收期,G1 GC从老年区间移动存活对象到空闲区间,这些空闲区间也就成为了老年代的一部分。G1的老年代回收器不需要整个老年代被回收,一次只需要扫描/回收一小部分老年代的Region就可以了。同时,这个老年代Region是和年轻代一起被回收的。
YGC
- JVM启动时,G1 先准备好Eden区,程序在运行过程中不断创建对象到Eden区,当Eden空间耗尽时,G1会启动一次YGC过程。
- YGC只会回收Eden区(主动)和Survivor区(被动)。
- YGC时,首先G1停止应用程序的执行,G1创建回收集(Collection Set),回收集是指需要被回收的内存分段的集合,YGC的
回收集
包含年轻代Eden区和Survivor区所有的内存分段。
然后开始如下回收过程:
- 第一阶段,扫描GC Roots
- GC Roots引用 + RSet记录的外部引用作为扫描存活对象的入口。
- 第二阶段,更新RSet
- 处理dirty card queue中的card,更新RSet。 此阶段完成后,RSet可以准确的反映老年代对新生代对象的引用。
- dirty card queue: 对于应用程序的引用赋值语句object.field=object,JVM会在之前和之后执行特殊的操作以在dirty card queue中入队一个保存了对象引用信息的card。
- 在年轻代回收的时候, G1会对Dirty Card Queue中所有的card进行处理,以更新RSet,保证RSet实时准确的反映引用关系。
- 那为什么不在引用赋值语句处直接更新RSet呢?这是为了性能的需要,RSet的处理需要线程同步,开销会很大,使用队列性能会好很多。
- 第三阶段,处理RSet。
识别被老年代对象指向的Eden中的对象,这些被指向的Eden中的对象被认为是存活的对象。 - 第四阶段,复制对象。
此阶段,对象树被遍历,Eden区中存活的对象会被复制到Survivor区中空的Region,Survivor区Region中存活的对象如果年龄未达阈值,年龄会加1,达到阀值会被会被复制到old区中空的Region。如果Survivor空间不够,Eden空间的部分数据会直接晋升到老年代空间。 第五阶段,处理引用。
处理Soft,Weak, Phantom, Final, JNI Weak等引用。最终Eden空间的数据为空,GC停止工作,而目标内存中的对象都是连续存储的,没有碎片,所以复制过程可以达到内存整理的效果,减少碎片。年轻代GC+并发标记
初始标记阶段:标记从根节点直接可达的对象。这个阶段是STW的,并且会触发一次年轻代GC。
- 根区域扫描(Root Region Scanning) : G1 GC扫描Survivor区直接可达的老年代区域对象,并标记被引用的对象。这一过程必须在young GC之前完成。
- 并发标记(Concurrent Marking): 在整个堆中进行并发标记(和应用程序并发执行),此过程可能被young GC中断。在并发标记阶段,若发现Region中的所有对象都是垃圾,那这个Region会被立即回收。同时,并发标记过程中,会计算每个Region的对象活性(Region中存活对象的比例)。
- 再次标记(Remark): 由于应用程序持续进行,需要修正上一次的标记结果。是STW的。G1中采用了比CMS更快的初始快照算法:snapshot-at-the-beginning (SATB)。
- 独占清理(cleanup,STW):计算各个区域的存活对象和GC回收比例,并进行排序,识别可以混合回收的区域。为下阶段做铺垫。是STW的。这个阶段并不会实际上去做垃圾的收集
-
混合回收
当越来越多的对象晋升到老年代Old Region时,为了避免堆内存被耗尽,虚拟机会触发一个混合的垃圾收集器,即Mixed GC, 该算法并不是一个Old GC,除了回收整个Young Region,还会回收一部分的Old Region。这里需要注意:是一部分老年代, 而不是全部老年代。可以选择哪些Old Region进行收集,从而可以对垃圾回收的耗时时间进行控制。也要注意的是Mixed GC并不是Full GC。
并发标记结束以后,老年代中百分百为垃圾的内存分段被回收了,部分为垃圾的内存分段被计算了出来。默认情况下,这些老年代的内存分段会分8次(可以通过
-XX:G1MixedGCCountTarget
设置)被回收。- 混合回收的回收集(Collection Set) 包括八分之一的老年代内存分段,Eden区内存分段,Survivor区内存分段。混合回收的算法和年轻代回收的算法完全一样,只是回收集多了老年代的内存分段。具体过程请参考上面的年轻代回收过程。
- 由于老年代中的内存分段默认分8次回收,G1会优先回收垃圾多的内存分段。垃圾占内存分段比例越高的,越会被先回收。并且有一个阈值会决定内存分段是否被回收,
-XX:G1MixedGCLiveThresholdPercent
,默认为65%,意思是垃圾占内存分段比例要达到65%才会被回收。如果垃圾占比太低,意味着存活的对象占比高,在复制的时候会花费更多的时间。 - 混合回收并不一定要进行8次。有一个阈值
-XX:G1HeapWastePercent
,默认值为10%,意思是允许整个堆内存中有10%的空间被浪费,意味着如果发现可以回收的垃圾占堆内存的比例低于10%,则不再进行混合回收。因为GC会花费很多的时间但是回收到的内存却很少。
混合GC的开始,基于G1 YGC的完成,并发标记线程的任务在创建后也不是直接启动,并发标记的启动依赖于YGC,在YGC的最后阶段会调用doConcurrentMark
方法来启动。判断启动的条件:当老年代使用的内存加上本次即将分配的内存占到总内存的45%,就表明需要启动混合GC,然后就要启动并发线程。
Full GC
G1的初衷就是要避免Full GC的出现。但是如果上述方式不能正常工作,G1会停止应用程序的执行(Stop The World),使用单线程的内存回收算法进行垃圾回收,性能会非常差,应用程序停顿时间会很长。 :::warning 要避免Full GC的发生,一旦发生,需要进行调整。 ::: 导致G1Full GC的原因可能有两个:
- Evacuation的时候没有足够的to-space来存放晋升的对象;
-
总结G1的垃圾回收
G1提供了两种GC模式,Young GC和Mixed GC。
Young GC:选定所有年轻代里的Region。通过控制年轻代的region个数,即年轻代内存大小,来控制young GC的时间开销。
- Mixed GC:选定所有年轻代里的Region,外加根据global concurrent marking统计得出收集收益高的若干老年代Region。在用户指定的开销目标范围内尽可能选择收益高的老年代Region。
:::warning
由上面的描述可知,Mixed GC不是Full GC,它只能回收部分老年代的Region,如果mixed GC实在无法跟上程序分配内存的速度,导致老年代填满无法继续进行Mixed GC,就会使用serial old GC(Full GC)来收集整个GC heap。
:::
global concurrent marking
执行过程类似CMS,但是不同的是,在G1 GC中,它主要是为Mixed GC提供标记服务的,并不是一次GC过程的一个必须环节。
global concurrent marking的执行过程分为四个步骤:
- 初始标记(initial mark,STW)。它标记了从GC Root开始直接可达的对象。
- 并发标记(Concurrent Marking)。这个阶段从GC Root开始对heap中的对象标记,标记线程与应用程序线程并行执行,并且收集各个Region的存活对象信息。
- 最终标记(Remark,STW)。标记那些在并发标记阶段发生变化的对象,将被回收。
- 清除垃圾(Cleanup)。清除空Region(没有存活对象的),加入到free list。 :::warning 第一阶段initial mark是共用了Young GC的暂停,这是因为他们可以复用root scan操作,所以可以说global concurrent marking是伴随Young GC而发生的。 :::
:::warning 第四阶段Cleanup只是回收了没有存活对象的Region,所以它并不需要STW。 ::: 什么时候发生Mixed GC呢?其实是由一些参数控制着的,另外也控制着哪些老年代Region会被选入CSet。
- G1HeapWastePercent:在global concurrent marking结束之后,我们可以知道old gen regions中有多少空间要被回收,在每次YGC之后和再次发生Mixed GC之前,会检查垃圾占比是否达到此参数,只有达到了,下次才会发生Mixed GC。
- G1MixedGCLiveThresholdPercent:old generation region中的存活对象的占比,只有在此参数之下,才会被选入CSet。
- G1MixedGCCountTarget:一次global concurrent marking之后,最多执行Mixed GC的次数。
G1OldCSetRegionThresholdPercent:一次Mixed GC中能被选入CSet的最多old generation region数量。
记忆集与写屏障
参考文章
问题
一个对象被不同区域引用的问题(分代引用问题)
- 一个Region不可能是孤立的,一个Region中的对象可能被其他任意Region中对象引用,判断对象存活时,扫描整个Java堆成本太高。
- 在其他的分代收集器,也存在这样的问题( 而G1更突出)
回收新生代也不得不同时扫描老年代?这样的话会降低MinorGC的效率;
解决办法
无论G1还是其他分代收集器,JVM都是使用RememberedSet来避免全局扫描;
- 每个Region都有一个对应的Remembered Set;
- 每次Reference类型数据写操作时,都会产生一个Write Barrier暂时中断操作;
- 然后检查将要写入的引用指向的对象是否和该Reference类型数据在不同的Region(其他收集器:检查老年代对象是否引用了新生代对象)
- 如果不同,通过CardTable把相关引用信息记录到引用指向对象的所在Region对应的Remembered Set中;
- 当进行垃圾收集时,在GC根节点的枚举范围加入Remembered Set;就可以保证不进行全局扫描,也不会有遗漏。
Full GC产生的原因
- System.gc()方法的调用
- 此方法的调用是建议JVM进行Full GC,虽然只是建议而非一定,但很多情况下它会触发 Full GC,从而增加Full GC的频率,也即增加了间歇性停顿的次数。
- 强烈建议能不使用此方法就别使用,让虚拟机自己去管理它的内存,可通过通过-XX:+ DisableExplicitGC来禁止RMI调用System.gc。
- 老年代空间不足
- 老年代空间在新生代对象转入、创建大对象大数组时才会出现不足的现象,当执行Full GC后空间仍然不足,则抛出如下错误:java.lang.OutOfMemoryError: Java heap space
- 为避免以上两种状况引起的Full GC,调优时应尽量做到让对象在Minor GC阶段被回收、让对象在新生代多存活一段时间及不要创建过大的对象及数组。
- 永生区空间不足
- JVM规范中运行时数据区域中的方法区,Permanet Generation中存放的为一些class的信息、常量、静态变量等数据。
- 当系统中要加载的类、反射的类和调用的方法较多时,Permanet Generation可能会被占满,在未配置为采用CMS GC的情况下也会执行Full GC。如果经过Full GC仍然回收不了,那么JVM会抛出如下错误信息:java.lang.OutOfMemoryError: PermGen space
- 为避免Perm Gen占满造成Full GC现象,可采用的方法为增大Perm Gen空间或转为使用CMS GC。
- 元空间不足
- CMS GC时出现promotion failed和concurrent mode failure
- 对于采用CMS进行老年代GC的程序而言,尤其要注意GC日志中是否有promotion failed和concurrent mode failure两种状况,当这两种状况出现时可能会触发Full GC。
- promotion failed:是在进行Minor GC时,survivor space放不下、对象只能放入老年代,而此时老年代也放不下造成的;
- concurrent mode failure:是在执行CMS GC的过程中同时有对象要放入老年代,而此时老年代空间不足造成的(有时候”空间不足”是CMS GC时当前的浮动垃圾过多导致暂时性的空间不足触发Full GC)。
- 措施为:增大survivor space、老年代空间或调低触发并发GC的比率,但在JDK 5.0+、6.0+的版本中有可能会由于JDK的bug29导致CMS在remark完毕后很久才触发sweeping动作。对于这种状况,可通过设置-XX: CMSMaxAbortablePrecleanTime=5(单位为ms)来避免
- 对于采用CMS进行老年代GC的程序而言,尤其要注意GC日志中是否有promotion failed和concurrent mode failure两种状况,当这两种状况出现时可能会触发Full GC。
- 统计得到的Minor GC晋升到老年代的平均大小大于老年代的剩余空间
- 这是一个较为复杂的触发情况,Hotspot为了避免由于新生代对象晋升到老年代导致老年代空间不足的现象,在进行Minor GC时,做了一个判断,如果之前统计所得到的Minor GC晋升到老年代的平均大小大于老年代的剩余空间,那么就直接触发Full GC。
- 例如程序第一次触发Minor GC后,有6MB的对象晋升到老年代,那么当下一次Minor GC发生时,首先检查老年代的剩余空间是否大于6MB,如果小于6MB,则执行Full GC。
- 当新生代采用PS GC时,方式稍有不同,PS GC是在Minor GC后也会检查,例如上面的例子中第一次Minor GC后,PS GC会检查此时老年代的剩余空间是否大于6MB,如小于,则触发对老年代的回收。
- 对于使用RMI来进行RPC或管理的Sun JDK应用而言,默认情况下会一小时执行一次Full GC。可通过在启动时通过- java -Dsun.rmi.dgc.client.gcInterval=3600000来设置Full GC执行的间隔时间或通过-XX:+ DisableExplicitGC来禁止RMI调用System.gc。
- 堆中分配很大的对象
- 所谓大对象,是指需要大量连续内存空间的java对象,例如很长的数组,此种对象会直接进入老年代,而老年代虽然有很大的剩余空间,但是无法找到足够大的连续空间来分配给当前对象,此种情况就会触发JVM进行Full GC。
- 为了解决这个问题,CMS垃圾收集器提供了一个可配置的参数,即-XX:+UseCMSCompactAtFullCollection开关参数,用于在”享受”完Full GC服务之后额外免费赠送一个碎片整理的过程,内存整理的过程无法并发的,空间碎片问题没有了,但提顿时间不得不变长了,JVM设计者们还提供了另外一个参数 -XX:CMSFullGCsBeforeCompaction,这个参数用于设置在执行多少次不压缩的Full GC后,跟着来一次带压缩的。
GC调优的标准
如果满足下面的指标,则一般不需要进行GC调优:
■ Minor GC执行时间不到50ms;
■ Minor GC执行不频繁,约10秒一次;
■ Full GC执行时间不到1s;
■ Full GC执行频率不算频繁,不低于10分钟1次。参考文章
JDK版本历史
JVM 发生 OOM 的 8 种原因、及解决办法 | HeapDump性能社区
警惕大量类加载器的创建导致诡异的Full GC | HeapDump性能社区
关于内存溢出,HashMap扩容导致StringBuilder扩大 | HeapDump性能社区
JVM源码分析之堆外内存完全解读 | HeapDump性能社区
Java中9种常见的CMS GC问题分析与解决 - 美团技术团队