垃圾回收器的发展历史
1999年:随JDK1.3.1一起来的串行方式Serial GC(第一款GC),ParNew GC是SerialGC的多线程版本。
2002年:2月26日,Parallel GC和Concurrent Mark Sweep GC(即CMS)跟随JDK1.4.2一起发布。Parallel GC在JDK6之后成为Hotspot默认GC。
2012年:在JDK1.7u4中,G1可用。
2017年:JDK9中G1成为默认垃圾回收器,以替代CMS.
2018年:3月,JDK10中G1的并行完整垃圾回收,实现并行性能改善最坏情况的延迟。
9月,JDK11发布,引入Epsilon GC,又称为“No-Op无操作”回收器,同时引入ZGC:可伸缩的低延迟回收器(Experimental)
2019年:3月,JDK12发布。增加G1,自动返回未使用堆内存给操作系统;同时,引入Shenandoah GC:地停顿时间的GC(Experimental)
9月,JDK13发布。增强ZGC,自动返回未使用堆内存给操作系统。
2020年:3月,JDK14发布。删除CMS,扩展ZGC,在mac和windows的应用。
**串行GC:**采用单线程回收垃圾,用户线程处于等待状态,适合于堆内存空间比较小和个人小项目
**并行GC:**多条垃圾收集线程并行工作,但用户线程仍然处于等待状态。
**并发GC:**用户线程和垃圾收集线程同时执行,他们运行于不同的CPU上。(不一定是并行,可能是交替执行)
-XX:+UseSerialGC:开启此参数使用serial & serial old搜集器(client模式默认值)。
-XX:+UseParNewGC:开启此参数使用ParNew & serial old搜集器(不推荐)
-XX:+UseConcMarkSweepGC:开启此参数使用ParNew & CMS(serial old为替补)搜集器
-XX:+UseParallelGC:开启此参数使用parallel scavenge & parallel old搜集器(server模式默认值)
-XX:+UseParallelOldGC:开启此参数在年老代使用parallel old搜集器(该参数在JDK1.5之后已无用)。
SerialGC
[DefNew: 139776K->17472K(157248K), 0.0164545 secs] 139776K->45787K(506816K), 0.0165501 secs] [Times: user=0.00 sys=0.02, real=0.02 secs] 2021-09-09T14:44:04.853+0800: 0.203: [GC (Allocation Failure) 2021-09-09T14:44:04.853+0800: 0.203: [DefNew: 157248K->17471K(157248K), 0.0192998 secs] 185563K->84401K(506816K), 0.0193485 secs] [Times: user=0.02 sys=0.01, real=0.02 secs]
2021-09-09T20:43:46.841+0800: 0.375: [Full GC (Allocation Failure) 2021-09-09T20:43:46.841+0800: 0.375:
[Tenured: 174631K->174502K(174784K), 0.0228937 secs] 252975K->195967K(253440K),
[Metaspace: 2760K->2760K(1056768K)], 0.0230051 secs] [Times: user=0.02 sys=0.00, real=0.02 secs]
收集器的名称为 DefNew(Default New Generation) 老年代为 Tenured
ParallelGC
2021-09-09T22:08:14.204+0800: 0.393:
[GC (Allocation Failure)
[PSYoungGen: 153077K->21497K(153088K)] 446120K->355171K(502784K), 0.0096928 secs] [Times: user=0.05 sys=0.11, real=0.01 secs]
2021-09-09T22:08:14.214+0800: 0.403:
[Full GC (Ergonomics)
[PSYoungGen: 21497K->0K(153088K)]
[ParOldGen: 333673K->249484K(349696K)] 355171K->249484K(502784K),
[Metaspace: 2760K->2760K(1056768K)], 0.0326259 secs] [Times: user=0.31 sys=0.00, real=0.03 secs]
收集器的名称 PsYoungGen 老年代的微 ParOldGen
UseConcMarkSweepGC
2021-09-10T10:04:53.054+0800: 0.327: [GC (CMS Initial Mark) [1 CMS-initial-mark: 205370K(349568K)] 223428K(506816K), 0.0001030 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
2021-09-10T10:04:53.054+0800: 0.327: [CMS-concurrent-mark-start]
2021-09-10T10:04:53.057+0800: 0.330: [CMS-concurrent-mark: 0.003/0.003 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
2021-09-10T10:04:53.057+0800: 0.330: [CMS-concurrent-preclean-start]
2021-09-10T10:04:53.058+0800: 0.331: [CMS-concurrent-preclean: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
2021-09-10T10:04:53.058+0800: 0.331: [CMS-concurrent-abortable-preclean-start]
==================================中间穿插着MinorGC================================
2021-09-10T10:04:53.076+0800: 0.349: [GC (Allocation Failure) 2021-09-10T10:04:53.076+0800: 0.349: [ParNew: 157248K->17472K(157248K), 0.0156403 secs] 362618K->266214K(506816K), 0.0157138 secs] [Times: user=0.01 sys=0.00, real=0.02 secs]
2021-09-10T10:04:53.113+0800: 0.387: [GC (Allocation Failure) 2021-09-10T10:04:53.113+0800: 0.387: [ParNew: 157248K->17472K(157248K), 0.0155434 secs] 405990K->308435K(506816K), 0.0155941 secs] [Times: user=0.05 sys=0.02, real=0.02 secs]
2021-09-10T10:04:53.149+0800: 0.423: [GC (Allocation Failure) 2021-09-10T10:04:53.149+0800: 0.423: [ParNew: 157074K->17472K(157248K), 0.0173170 secs] 448038K->353000K(506816K), 0.0173738 secs] [Times: user=0.05 sys=0.00, real=0.02 secs]
==================================================================================
2021-09-10T10:04:53.167+0800: 0.440: [CMS-concurrent-abortable-preclean: 0.003/0.109 secs] [Times: user=0.17 sys=0.02, real=0.11 secs]
----------------------------------最终标记------------------------------------
2021-09-10T10:04:53.167+0800: 0.440: [GC (CMS Final Remark)
[YG occupancy: 20972 K (157248 K)]2021-09-10T10:04:53.167+0800: 0.440:
[Rescan (parallel) , 0.0002117 secs]2021-09-10T10:04:53.167+0800: 0.440:
[weak refs processing, 0.0000083 secs]2021-09-10T10:04:53.167+0800: 0.441:
[class unloading, 0.0002509 secs]2021-09-10T10:04:53.168+0800: 0.441:
[scrub symbol table, 0.0003192 secs]2021-09-10T10:04:53.168+0800: 0.441:
[scrub string table, 0.0001136 secs]
[1 CMS-remark: 335528K(349568K)] 356500K(506816K), 0.0009630 secs]
[Times: user=0.00 sys=0.00, real=0.00 secs]
2021-09-10T10:04:53.168+0800: 0.441: [CMS-concurrent-sweep-start]
2021-09-10T10:04:53.169+0800: 0.442: [CMS-concurrent-sweep: 0.001/0.001 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
2021-09-10T10:04:53.169+0800: 0.442: [CMS-concurrent-reset-start]
2021-09-10T10:04:53.169+0800: 0.442: [CMS-concurrent-reset: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
新生代的收集器为 ParNew 老年代就是CMS了
日志分析
2021-09-09T22:08:14.204+0800: 0.393:
[GC (Allocation Failure)
[PSYoungGen: 153077K->21497K(153088K)] 446120K->355171K(502784K), 0.0096928 secs] [Times: user=0.05 sys=0.11, real=0.01 secs]
网上找的一段例子
- 2021-09-09T22:08:14.204+0800: 0.393-GC事件开始的时间点。+0800表示当前时区为东八区,这只是一个标识。0.393是GC事件相对于JVM启动时间的间隔,单位是秒
- GC - 用来区分Minor GC还是Full GC的标志。GC表明这是一次小型GC(Minor GC),即年轻代GC
- GC cause 。触发GC的原因、这里是 Allocation Failure。对象分配失败导致GC。这个 GC cause 后面详细分析
PSYoungGen
:垃圾收集器的名称,这个名称表示的是在年轻代中使用的:并行的标记-复制
,STW垃圾收集器153077K->21497K(153088K)
表示年轻代回收前->回收后(年轻代内存大小),GC后新生代使用率为 21497K / 153088K= 14%446120K->355171K(502784K)
表示堆内存回收前大小->堆内存回收后大小(堆内存大小),GC后堆内存使用率为 355171K / 502784K = 70%,使用率并不低[Times: user=0.05 sys=0.11, real=0.01 secs]
:GC事件的持续时间,通过三个部分衡量,user
表示GC线程所消耗的总CPU时间,sys
表示操作系统和系统等待事件所消耗的时间,real
则表示应用程序实际暂停时间。由于并不是所有的操作过程都能全部并行,所以在并行GC中,real 的值可能会小于 user + sys 的值。四舍五入的结果
在做性能优化时,我们一般采用 real 时间来优化程序。因为最终用户只关心点击页面发出请求到页面上展示出内容所花的时间,也就是响应时间,而不关心你到底使用了多少个 GC 线程或者处理器。但并不是说 sys 和 user 两个时间不重要,当我们想通过增加 GC 线程或者 CPU 数量来减少 GC 停顿时间时,可以参考这两个时间
2021-09-09T22:08:14.214+0800: 0.403:
[Full GC (Ergonomics)
[PSYoungGen: 21497K->0K(153088K)]
[ParOldGen: 333673K->249484K(349696K)] 355171K->249484K(502784K),
[Metaspace: 2760K->2760K(1056768K)], 0.0326259 secs] [Times: user=0.31 sys=0.00, real=0.03 secs]
Metaspace: 2760K->2760K(1056768K)
元空间并没有被回收掉任何对象
GC cause
JVM 参数 -XX:+PrintGCCause 来打印 gc cause
/*
* Copyright (c) 2002, 2013, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*
*/
#include "precompiled.hpp"
#include "gc_interface/gcCause.hpp"
const char* GCCause::to_string(GCCause::Cause cause) {
switch (cause) {
case _java_lang_system_gc:
return "System.gc()";
case _full_gc_alot:
return "FullGCAlot";
case _scavenge_alot:
return "ScavengeAlot";
case _allocation_profiler:
return "Allocation Profiler";
case _jvmti_force_gc:
return "JvmtiEnv ForceGarbageCollection";
case _gc_locker:
return "GCLocker Initiated GC";
case _heap_inspection:
return "Heap Inspection Initiated GC";
case _heap_dump:
return "Heap Dump Initiated GC";
case _wb_young_gc:
return "WhiteBox Initiated Young GC";
case _update_allocation_context_stats_inc:
case _update_allocation_context_stats_full:
return "Update Allocation Context Stats";
case _no_gc:
return "No GC";
case _allocation_failure:
return "Allocation Failure";
case _tenured_generation_full:
return "Tenured Generation Full";
case _metadata_GC_threshold:
return "Metadata GC Threshold";
case _cms_generation_full:
return "CMS Generation Full";
case _cms_initial_mark:
return "CMS Initial Mark";
case _cms_final_remark:
return "CMS Final Remark";
case _cms_concurrent_mark:
return "CMS Concurrent Mark";
case _old_generation_expanded_on_last_scavenge:
return "Old Generation Expanded On Last Scavenge";
case _old_generation_too_full_to_scavenge:
return "Old Generation Too Full To Scavenge";
case _adaptive_size_policy:
return "Ergonomics";
case _g1_inc_collection_pause:
return "G1 Evacuation Pause";
case _g1_humongous_allocation:
return "G1 Humongous Allocation";
case _last_ditch_collection:
return "Last ditch collection";
case _last_gc_cause:
return "ILLEGAL VALUE - last gc cause - ILLEGAL VALUE";
default:
return "unknown GCCause";
}
ShouldNotReachHere();
}
system.gc
调用 System.gc() 时触发
注意,调用该方法只是额外启动一个方法然后在合适的时间去执行 GC。所以它并不能保证立刻触发 GC(和JVM执行GC有关),也不能保证一定会执行 GC(和垃圾回收器有关),也不能保证触发的 GC 时 Young GC 还是 Full GC(和垃圾回收器有关)
如果想禁用显式 GC(默认不禁用):-XX:+DisableExplicitGC
FullGCAlot
JDK 的调试版本可以设置,用来定期触发 GC,我自己的项目中测试时提示如下异常:
ScavengeAlot
JDK 的调试版本可以设置,用来定期触发 GC,我自己的项目中测试时提示如下异常:
Allocation Profiler
使用 -Xaprof 设置运行项目,它会在 jvm 退出之前触发。 -Xaprof 选项已在 java 8 中删除
JvmtiEnv ForceGarbageCollection
JVMTI 是 Java 虚拟机所提供的 native 编程接口,用来对 Java 运行态测试和分析,常见的实现就是各种各样的 Java Agent 了
这个 GC 的产生原因,其实就是你自己强制调用 GC
GCLocker Initiated GC
当使用本地方法 JNI 函数访问 JVM 中的字符串或数组数据,为保证数据安全性和准确性,获取数据(GetStringCritical)和释放数据(ReleaseStringCritical)这段过程必须在 “critical region”(临界区)执行 如果在执行临界区这段时间发生了 GC,JVM 会阻断 GC 的发生,也会阻断其他线程进入临界区,同时调 ReleaseStringCritical。当最后一个临界区的线程退出后,JVM 再进行一个 GC,即 GCLocker Initiated GC
Heap Inspection Initiated GC
该 GC 是当你对堆进行检查时触发,使用如下命令才能出现
jmap -histo:live <pid>
Heap Dump Initiated GC
该 GC 是当你对堆进行转储时触发,使用如下命令才能出现
jmap -dump:live,format=b,file=heap.out <pid>
WhiteBox Initiated Young GC
测试时主动触发的 Young GC,需要增加 WhiteBox 的 Agent 才能使用,后边会讲 WhiteBox
Update Allocation Context Stats
原为:GCs can be initiated solely for the purpose of getting updated allocation context statistics; these GCs should be identifiable by a unique GCCause. Add a new cause “_update_allocation_context_stats”.这个 GC 仅用于获取更新的分配上下文统计信息。具体参照:JDK-8058235 : identify GCs initiated to update allocation context stats
No GC
用来指示 CMS 并发标记的阶段
Allocation Failure
Allocation Failure 表示向 Young generation(eden) 给新对象申请空间,但是 Young generation(eden) 剩余的合适空间不够所需的大小导致的 Young GC,在 G1 垃圾回收器中非常常见这个 GC
// 三个参数分别是:最大堆,初识堆,年轻代
-Xmx
-Xms
-Xmn
// 年轻代中eden和survivor的比例,默认8:1:1,即SurvivorRatio=8,SurvivorRatio=4时意味着比例为 4:1:1
–XX:SurvivorRatio
// 年轻代和老年代比例,默认1:2
-xx:newratio
Tenured Generation Full
是针对老年代的一个 Full GC。原因是老年代的对象长期存在并不断有新的生成从而导致 Tenured Generation 满了,最后触发 GC
Metadata GC Threshold
// 初识元空间大小,默认 21MB
-XX:MetaspaceSize=128
// 最大元空间大小
-XX:MaxMetaspaceSize=128
CMS Generation Full
CMS Initial Mark
CMS Final Remark
CMS Concurrent Mark
Old Generation Expanded On Last Scavenge
Old Generation Too Full To Scavenge
Ergonomics
这种异常会发生在 Parallel Scavenge+Serial Old 的组合下,如果老生代的剩余空间少于下一次收集所需的剩余空间,那么现在就做一个 Full GC,其实就是虚拟机估算出下次分配可能会发生无法分配的问题,于是提前发生一次 Full GC。详细参考读懂一行Full GC日志(回复JVM内存分配担保机制一文中 Mr/Mrs Xxx 在留言区提出的问题)
注意:JDK 1.8 默认使用 UseParallelGC 垃圾回收器,该垃圾回收器默认启动了 AdaptiveSizePolicy,会根据 GC 的情况自动计算计算 Eden、From 和 To 区的大小
G1 Evacuation Pause
当没有更多的空闲 region 被提升到老一代或者复制到幸存空间时,并且由于堆已经达到最大值,堆不能扩展,从而发生 Evacuation Failure
G1 Humongous Allocation
Humongous(大型对象)是大于 G1 中 region 大小 50% 的对象。在大量分配之前,jvm 会检查它是否应该执行例行 Evacuation Pause 而不考虑实际分配大小,但如果由于此检查而触发,则将被列为大量分配。此情况也用于任何用于为分配释放足够空间的集合
Last ditch collection
对于 perm gen(java 7 或更早版本)和元空间(java 8+),如果分配失败并且无法扩展内存池时触发该 GC,表象为已经进行了一个 Metadata GC,然后又触发了 Last ditch collection
原因多为 metaspace 的内存碎片化,即引用过多且没有清除
举例:大量使用反射,在 Java 虚拟机中,每个类加载器都有一个 ClassLoaderData 的数据结构,ClassloaderData 内部有管理内存的 Metaspace,Metaspace 在 initialize 的时候会调用 get_initialization_chunk 分配第一块 Metachunk,类加载器在类的时候是以 Metablock 为单位来使用 Metachunk。当创建很多类加载器,而每个类加载器又加载了很少的类,即会出现该问题。详细参考:记一次诡异的频繁Full GC 大量类加载器创建导致诡异FullGC
ILLEGAL VALUE - last gc cause - ILLEGAL VALUE
Included for completeness,正常情况看不到此信息
unknown GCCause
https://juejin.cn/post/7029130033268555807
https://blog.51cto.com/u_10980859/5340819
https://cloud.tencent.com/developer/news/730553
https://cloud.tencent.com/developer/article/1082687
https://www.jianshu.com/p/9916ae406f56