JVM 之 内存模型与垃圾回收机制(GC)
摘要
-
本文介绍JVM的内存模型与垃圾回收机制
JVM虚拟机结构(HotSpot)
-
设置内存分配示例
1 | # jdk1.8+ |
-
各参数含义与默认值(针对 JDK 1.8)
参数 | 含义说明 | 默认值(JDK 1.8) |
---|---|---|
-Xms2048M |
初始堆大小,即 JVM 启动时分配的堆内存大小(这里是 2GB) | 物理内存的 1/64(最小 1MB),推荐配置为与 -Xmx 一致 |
-Xmx2048M |
最大堆内存大小,JVM 允许分配的最大堆内存 | 物理内存的 1/4(受限于 32位/64位) |
-Xmn1024M |
新生代大小(Eden + Survivor)为 1GB | 未显式指定时,通常占堆的 1/3 左右 |
-Xss512K |
每个线程的栈大小(Thread Stack Size),这里设置为 512KB | 1MB(64位系统)或 512KB(32位系统) |
-XX:MetaspaceSize=256M |
元空间初始大小(用于加载类的元数据,不再使用 PermGen),达到该值后,JVM 会触发一次 GC,空间不够时会进行扩容,最大到 MaxMetaspaceSize |
默认 21MB(客户端)或 16MB(服务器端),为避免频繁扩容导致的GC,可以设置的稍微大一些,比如MaxMetaspaceSize 的一半,或干脆与MaxMetaspaceSize 一样大 |
-XX:MaxMetaspaceSize=256M |
元空间最大大小,接近设置值时会触发 Full GC | 无限制(默认只受物理内存约束) ,推荐配置一个合适的数值,比如8G的内存可以配置为256M |
类装载子系统(Class Loading Subsystem)
-
类装载子系统负责将
.class
文件加载到 JVM 中,并进行解析、验证、初始化等过程。 -
加载过程的几个阶段:
阶段 | 说明 |
---|---|
加载(Loading) | 将 .class 文件读取为二进制数据,构造 Class 对象 |
验证(Verification) | 确保字节码文件格式正确、安全合法(防止恶意代码) |
准备(Preparation) | 为类的静态变量分配内存,并设置默认初始值 |
解析(Resolution) | 将常量池中的符号引用替换为直接引用(方法、字段等) |
初始化(Initialization) | 执行 <clinit> 静态初始化方法,对静态变量赋初始值 |
-
类加载器(ClassLoader)体系结构:详细参考 JVM 之 类加载器
类加载器名称 | 加载内容 | 说明 |
---|---|---|
引导类加载器(Bootstrap ClassLoader) | Java 核心类库(如 java.lang.* ) |
由 JVM 自身实现,用本地代码实现,无法直接访问 |
扩展类加载器(Extension ClassLoader) | JAVA_HOME/jre/lib/ext 目录下的类 |
加载标准扩展类库 |
应用类加载器(Application ClassLoader) | 用户应用类路径(CLASSPATH 指定的目录) | 最常用,加载大多数应用代码 |
自定义类加载器(Custom ClassLoader) | 用户手动实现的类加载器 | 可以打破双亲委派机制,实现热加载、加密类加载等 |
类加载采用 双亲委派模型:请求会先向父加载器委托,只有在父加载器加载失败时才尝试自身加载。
字节码执行引擎(Execution Engine)
-
字节码执行引擎负责将 Java 字节码解释或编译为机器代码,并在底层平台上执行。
-
执行引擎的核心模块
组件名称 | 作用说明 | 关键特性 |
---|---|---|
解释器(Interpreter) | 将字节码逐条解释执行 | 启动快,适合冷代码(非热点代码),执行效率相对较低 |
即时编译器(JIT Compiler) | 将热点代码编译为本地机器码,提高执行效率 | 包含 C1(Client)和 C2(Server)两种,支持优化如:方法内联、逃逸分析、循环展开等 |
垃圾收集器(Garbage Collector, GC) | 自动内存管理,负责对象生命周期的回收 | 常见算法包括:Serial、Parallel、CMS、G1、ZGC(低延迟)等,根据不同场景选择 |
本地接口(Native Interface) | 支持 Java 与本地语言(如 C/C++)的互操作 | 通过 JNI(Java Native Interface)实现,调用底层操作系统或第三方库功能 |
JVM 内存模型(Java Memory Model,JMM)
-
JMM 是 Java 虚拟机规范中定义的一种 抽象内存模型,它决定了多线程程序中变量的读写可见性、有序性和原子性。同时,JVM 在物理层也有一个实际的内存结构,称为运行时数据区域(Runtime Data Areas),这两个可以结合理解。
-
JMM 的主要目标
- 保证多线程环境下的数据一致性
- 指导 JVM 和 CPU 的内存交互行为(如重排序、缓存)
-
实际运行时内存结构如下:
内存区域 | 说明 |
---|---|
📌 程序计数器(PC) | 每个线程私有,记录当前执行的字节码指令地址 |
📌 Java 线程栈 | 每个线程私有,方法调用时用于存储局部变量、操作数栈等 |
📌 本地方法栈 | 与虚拟机栈类似,用于 native 方法 |
📌 堆(Heap) | 所有线程共享,存储对象实例、数组等,GC 的主要区域 |
📌 方法区(或元空间) | 所有线程共享,存储类信息、静态变量、常量池等(JDK8 后称为 Metaspace) |
垃圾回收器
-
垃圾回收器负责自动管理内存,回收不再使用的对象,避免内存泄漏和溢出。
什么是垃圾
-
内存中没有被(线程栈变量,静态变量,常量池,JNI指针)引用的地址就是垃圾
-
可达性分析算法:是现代 JVM 判断一个对象是否“还活着”的主要算法。
1 | 基本思想: |
-
在 JVM 中,
GC Roots
是一些 始终可用的、不会被垃圾回收的引用起点,主要包括:
GC Roots 来源 | 说明 |
---|---|
当前线程栈中的引用(局部变量表) | 各个线程正在调用的方法中的局部变量、参数等 |
静态字段引用 | 类的静态字段引用的对象 |
JNI 引用(Native 方法引用) | Java 本地方法中引用的对象 |
常量引用池中的对象 | 字符串常量等可能持有对象引用 |
活动线程对象 | 线程自身在 GC 时不会被回收 |
JVM 内部结构(如系统类加载器等) | JVM 关键系统对象 |
垃圾回收器种类:
-
左边6种叫分代模型,右边的4种叫分区模型
分代模型(Generational Model)
堆内存划分为:
年轻代(Young Generation):存放新创建的对象,分为 Eden 和 Survivor 区。
老年代(Old Generation):存放经过多次 GC 后仍然存活的对象。
分区模型(Region-based Model)
分区模型(如 G1、ZGC、Shenandoah)不再严格按照代划分内存,而是把堆划分为多个 大小相同的 Region。每个 Region 可以在运行时被动态标记为 Eden、Survivor 或 Old。
-
分代模型中,上面3个是新生代垃圾回收器,下面3个是老年代垃圾回收器,可以交叉配对(见上图虚线),但最常用是上下两两配对。
-
CMS即可以作为新生代垃圾回收器,也可以作为老年代垃圾回收器。
-
EpsilonGC,是一个特殊的垃圾回收器,它不回收任何对象,只负责最终记录,做测试用的。
-
目前最先进的模型是
ZGC
,jkd11开始支持,但直到JDK16才比较完善,目前非默认配置,需要手动配置。其与Redhat出品的Shenandoah
是竞争关系。 -
常见垃圾回收器及其分类、JDK版本
垃圾回收器 | 所属模型 | 说明 | 首次出现 JDK 版本 | 启用命令(JVM 参数) | 适合的堆内存大小 |
---|---|---|---|---|---|
Serial | 分代模型 | 单线程,新生代和老年代都使用 Serial,适用于小堆内存, 已弃用 |
JDK 1.2 | -XX:+UseSerialGC |
💾 小于 1GB |
ParNew | 分代模型 | Serial 的多线程版本,仅用于 CMS 新生代, JDK11已弃用 |
JDK 1.4 | -XX:+UseParNewGC -XX:+UseConcMarkSweepGC |
💾 1GB ~ 4GB |
Parallel(吞吐量 GC) | 分代模型 | 多线程 GC,适合吞吐量优先的应用场景 | JDK 1.4 | -XX:+UseParallelGC (新生代)-XX:+UseParallelOldGC (老年代) |
💾 2GB ~ 8GB |
CMS(Concurrent Mark Sweep) | 分代模型 | 老年代并发标记-清除,低延迟,但存在碎片,JDK14已弃用 |
JDK 1.4 | -XX:+UseConcMarkSweepGC |
💾 2GB ~ 8GB |
G1(Garbage First) | 分区模型 | 将堆划分为 Region,逻辑分代,支持并发压缩,平衡延迟与吞吐 | JDK 7u4(正式) | -XX:+UseG1GC |
💾 4GB ~ 数十 GB |
ZGC(Z Garbage Collector) | 分区模型 | Region 弹性大小,支持超大堆,低延迟(<10ms 停顿) | JDK 11(实验),JDK 15(正式) | -XX:+UseZGC |
💾 8GB ~ 数 TB(超大堆) |
Shenandoah | 分区模型 | 红帽主导,低延迟,并发回收与并发压缩 | JDK 12(实验),JDK 15(正式) | -XX:+UseShenandoahGC |
💾 2GB ~ 数十 GB |
对于 CMS,启用后会自动使用 ParNew 作为新生代回收器(除非显式禁止)。
对于 Parallel GC 启用-XX:+UseParallelGC
(新生代)会自动启用-XX:+UseParallelOldGC
(老年代)。
对于ZGC,jdk15以前最大支持4T内存,之后最大支持16T内存。
-
各版本默认垃圾回收器及推荐配置(JDK 1.6 起)
JDK 版本 | 默认 GC | 建议使用 GC(按场景分类) |
---|---|---|
JDK 1.6 | Serial / Parallel | - 小型应用(如桌面程序):-XX:+UseSerialGC - 中大型应用(吞吐量优先): -XX:+UseParallelGC |
JDK 1.7 | 同上 | 同 JDK 1.6 |
JDK 1.8 | Parallel | - 吞吐优先:默认 -XX:+UseParallelGC - 响应优先: -XX:+UseConcMarkSweepGC (CMS)- 大堆 + 未来升级考虑: -XX:+UseG1GC (推荐) |
JDK 9-14 | G1 GC | - 一般默认即可:-XX:+UseG1GC (延迟与吞吐平衡)- 极端低延迟要求:升级到 JDK 11+ 使用 ZGC/Shenandoah |
JDK 15+ | G1 GC(默认),ZGC / Shenandoah 可选 | - 延迟敏感(在线服务、RT系统):-XX:+UseZGC 或 -XX:+UseShenandoahGC - 吞吐为主: -XX:+UseParallelGC - 综合平衡: -XX:+UseG1GC (默认) |
-
判断默认 GC 的方式
1 | java -XX:+PrintCommandLineFlags -version |
垃圾回收算法
-
JVM 中常见垃圾回收算法汇总
算法名称 | 核心思想 | 适用阶段/区域 | 优缺点简述 |
---|---|---|---|
标记-清除(Mark-Sweep) | 标记出存活对象,清除未标记对象 | 老年代 | 简单高效,但会产生大量碎片,不适合连续内存分配 |
标记-整理(Mark-Compact) | 标记后移动存活对象,整理碎片 | 老年代 | 消除碎片,代价是移动对象,适用于老年代压缩 |
复制算法(Copying) | 将对象复制到另一块内存(如 Eden → Survivor) | 新生代 | 高效率,适合回收大多数对象短命的新生代,但需要额外空间 |
分代回收(Generational) | 将对象按生命周期划分(新生代/老年代) | 整个堆结构 | 实用性强,结合不同算法应用于不同代,现代 GC 基础 |
分区回收(Region-based) | 将堆划分为若干等大小的 Region 动态分配 | 整个堆(如 G1、ZGC) | 更灵活,支持并发并行,减少 STW 停顿,适用于大堆、低延迟场景 |
增量回收(Incremental) | 分阶段小步执行 GC 以减少单次停顿 | 某些并发/低延迟 GC | 减少暂停时间,但整体效率可能降低 |
并发回收(Concurrent) | 标记、清理等步骤与应用线程并发执行 | CMS、G1、ZGC 等 | 停顿时间短,对响应时间要求高的系统友好 |
三色标记(Tri-color Marking) | 并发标记算法的一种实现思想 | CMS、G1、ZGC 等 | 白(待回收)、灰(已标记未扫描)、黑(已标记已扫描),避免“漏标”问题 |
SATB(Snapshot-At-The-Beginning) | 并发标记的快照策略 | G1、ZGC | 保证在并发标记过程中不遗漏新引用,适合高并发场景 |
Lazy Compaction(延迟压缩) | 不每次 GC 都压缩,视情况而定 | G1 等 | 降低不必要的移动成本 |
-
对应关系:GC 回收器和底层算法
回收器 | 使用的算法组合 |
---|---|
Serial | 新生代:复制算法 老年代:标记-整理 |
ParNew | 新生代:复制算法(多线程) |
Parallel | 新生代:复制算法 老年代:标记-整理 |
CMS | 新生代:ParNew(复制) 老年代:标记-清除 + 三色标记 + 并发 |
G1 | 分区回收 + 三色标记 + SATB + Lazy Compaction |
ZGC | 分区回收 + 并发标记 + SATB + Region Remapping |
Shenandoah | 分区回收 + 并发标记 + 并发压缩 + 三色标记 |
1 | ┌────────────────────────┐ |
什么是 STW(Stop-The-World)
-
STW(Stop-The-World) 指的是:在某些垃圾回收阶段,JVM 会暂停所有应用线程(也叫用户线程),让垃圾回收线程独占 CPU 执行 GC 逻辑。
-
你可以这样理解 STW
- JVM 会“按下暂停键”暂停所有正在运行的 Java 程序代码;
- 然后 专心进行 GC 的某些阶段(如标记、整理、复制等);
- GC 完成后,才会“恢复运行”应用线程。
-
各 GC 中 STW 的存在情况
回收器 | 是否存在 STW? | 说明 |
---|---|---|
Serial | ✅ 是,全停顿,全阶段单线程 | 堆越大 STW 越长 |
Parallel | ✅ 是,全停顿,多线程执行 GC | 提高效率但仍会暂停 |
CMS | ✅ 有,初始标记和最终重新标记是 STW | 大部分阶段并发执行 |
G1 | ✅ 有,但设计为尽可能缩短 STW | 分阶段并发 + 并行处理 |
ZGC | ✅ 极短(<10ms) | 仅个别阶段是 STW,几乎感知不到 |
Shenandoah | ✅ 极短 | 高度并发,STW 时间也极短 |
-
为什么要关注 STW?
- 在响应时间敏感型系统(如在线交易系统、游戏服务器、API 网关)中,长时间的 STW 会造成用户请求卡顿、超时。
- 因此,选择 低 STW 的 GC(如 G1、ZGC、Shenandoah) 对这类系统至关重要。
堆内存结构
-
不同的垃圾回收器决定了堆内存的结构不同,但总体上分为两种类型:分代模型和分区模型。
分代模型
Parallel 垃圾回收器
-
堆结构被划分为
Old Generation(老年代)
和Young Generation(新生代)
两部分。 -
Young Generation
由Eden
和 两个Survivor
组成,其中Eden
是一个连续的内存区域,Survivor
是一个非连续的内存区域。 -
默认情况下,
Young Generation
占堆内存的1/3
,Old Generation
占堆内存的2/3
。 -
默认情况下,
Eden:S0:S1 = 8:1:1
,如果希望为 4:1:1,使用-XX:SurvivorRatio=4
,但实际上这个比例并不是固定的,而是由jvm基于情况自动变化的,因为JVM默认开启了这个参数-XX:+UseAdaptiveSizePolicy
,如果希望固定这个比例,可以设置为-XX:-UseAdaptiveSizePolicy
来关闭这个配置。 -
默认情况下,对象最多经历
15次
Minor GC后进入老年代,可以通过-XX:MaxTenuringThreshold=n
设置。
区域 | 说明 |
---|---|
Eden | 对象首次创建的区域,大部分对象在这里创建并很快被回收 |
S0/S1 | Survivor 区域:两个交替使用的缓冲区(From 和 To),用于拷贝存活对象 |
Old | 老年代:存活次数多、生命周期长的对象会从新生代晋升到老年代,回收频率低 |
-
对象内存分配图解(简化)
1 | 创建对象 |
-
大对象直接进入老年代
- 大对象就是需要大量连续内存空间的对象(比如:字符串、数组),如果对象很大(如超过 Eden 区大小,或是超过 Survivor 区大小),可能直接分配到老年代
- 也可以通过
-XX:PretenureSizeThreshold=<大小,单位字节>
控制超过一定大小的对象是否直接分配到老年代(Old Generation),跳过新生代(Eden),以避免大对象频繁在年轻代造成 GC 压力。这个参数只在 Serial 和ParNew两个收集器下有效。
-
对象动态年龄判断
- 对象在Survivor区来回移动时,如果这批对象的总大小大于这块Survivor区域内存大小的50%(-XX:TargetSurvivorRatio可以指定),那么此时大于等于这批对象年龄最大值的对象,就可以直接进入老年代了,
- 例如Survivor区域里现在有一批对象,年龄1+年龄2+年龄n的多个年龄对象总和超过了Survivor区域的50%,此时就会把年龄n(含)以上的对象都放入老年代。这个规则其实是希望那些可能是长期存活的对象,尽早进入老年代。
- 对象动态年龄判断机制一般是在minor gc之后触发的。
-
对象逃逸分析
- 如果对象很小,且只在方法内部使用,并没有被外部引用,则其有可能直接分配到栈内存,而不进入堆内存
- JVM对于这种情况可以通过开启逃逸分析参数(-XX:+DoEscapeAnalysis)来优化对象内存分配位置,使其通过
标量替换
优先分配在栈上(栈上分配),JDK7之后默认开启逃逸分析,如果要关闭使用参数(-XX:-DoEscapeAnalysis)
-
标量替换
- 通过逃逸分析确定该对象不会被外部访问,并且对象可以被进一步分解时,JVM不会创建该对象,而是将该对象成员变量分解若干个被这个方法使用的成员变量所代替,这些代替的成员变量在栈帧或寄存器上分配空间,这样就不会因为没有一大块连续空间导致对象内存不够分配。
- 开启标量替换参数(-XX:+EliminateAllocations),JDK7之后默认开启。
-
标量与聚合量
- 标量即不可被进一步分解的量,而JAVA的基本数据类型就是标量(如:int,long等基本数据类型以及reference类型等),
- 标量的对立就是可以被进一步分解的量,而这种量称之为聚合量。而在JAVA中对象就是可以被进一步分解的聚合量。
Parallel 垃圾回收器(Parallel GC) 的工作方式
-
Parallel GC 是多线程垃圾回收器,可以通过
-XX:ParallelGCThreads
来设置GC线程数量。 -
当 Parallel GC 被触发(例如 Minor GC 或 Full GC)时,所有用户线程 被完全暂停(Stop-The-World, STW)
Parallel GC 参数配置表(吞吐量优先 GC)
参数名 | 说明 | 默认值(如未特别说明) |
---|---|---|
-XX:+UseParallelGC |
开启 Parallel GC,用于新生代收集 | - |
-XX:+UseParallelOldGC |
开启老年代并行收集(Parallel Old) | - |
-XX:ParallelGCThreads |
垃圾回收时的并行线程数(与 CPU 数量相关) | 根据硬件自动配置 |
-XX:MaxGCPauseMillis |
设置 GC 最大暂停时间目标(影响内存分配策略) | 一个非常大的值,可以认为无限制(建议按需设置) |
-XX:GCTimeRatio |
设置 GC 时间与应用运行时间的比值(0~100) 值越小,GC 越频繁,值越大 GC 越少 |
99(表示 1% 用于 GC,99% 用于应用) |
-XX:+UseAdaptiveSizePolicy |
启用自适应 GC 策略(根据运行状况自动调整各区域大小) | 默认开启 |
-XX:SurvivorRatio |
Eden 与 Survivor 的内存比例(如 8 表示 Eden:S0:S1 = 8:1:1) | 8 |
-XX:InitialTenuringThreshold |
对象晋升到老年代的初始年龄(会随运行动态调整) | 7 |
-XX:MaxTenuringThreshold |
晋升到老年代的最大年龄(对象在 Survivor 区经历几次 GC) | 15 |
-XX:PretenureSizeThreshold |
设置大对象阈值,超过该大小的对象直接分配到老年代 | 0(即禁用) |
-XX:+ScavengeBeforeFullGC |
在 Full GC 之前是否先执行一次 Minor GC | true启 |
-XX:+UseFastAccessorMethods |
优化原始类型的 get/set 方法性能 | false启 |
-XX:+AlwaysPreTouch |
JVM 启动时立即分配并初始化所有内存页,避免运行时首次分配带来的停顿 | false |
分区模型
G1 垃圾回收器
-
G1(Garbage-First) 属于 物理上分区,逻辑上分代,真正的分区模型是
ZGC
(jdk21后也支持分代) -
G1将Java堆划分为多个大小相等的Region,Region大小是2的幂,范围在1MB到32MB之间。JDK9之前默认最多支持2048个Region,这就导致G1最大支持64G 的堆 (
2048 Regions × 32MB = 64GB
),JDK10之后的版本可支持更多Region数量,JDK17进一步优化了Region映射机制,提升了大堆场景下的性能,使其可以支持更大的内存,但对于超大堆(>1T),考虑到GC暂停时间,建议使用ZGC或Shenandoah GC。 -
Region一旦被回收,重新分配时可以是任意类型,比如原先是
Eden
,那么重新分配时可以是Old
,反之亦然。 -
一般Region大小由JVM自动计算,当然也可以用参数
-XX:G1HeapRegionSize
手动指定Region大小,但是推荐默认的计算方式。 -
G1保留了年轻代和老年代的概念,但不再是物理隔阂了,它们都是(可以不连续)Region的集合。
-
在 G1 GC 中,不要使用
-Xmn
,G1 中推荐使用以下更细粒度的控制方式:
1 | # 这是实验性参数,需要开启 `-XX:+UnlockExperimentalVMOptions`,但依旧推荐在生产环境中使用 |
-
默认年轻代对堆内存的占比是
5%
,如果堆大小为4096M,那么年轻代占据200MB左右的内存,对应大概是100个Region,可以通过-XX:G1NewSizePercent
设置新生代初始占比,这是实验性参数,需要开启-XX:+UnlockExperimentalVMOptions
,但依旧推荐在生产环境中使用。 -
在系统运行中,JVM会不停的给年轻代增加更多的Region,但是最多新生代的占比不会超过
60%
,可以通过-XX:G1MaxNewSizePercent
调整,这是实验性参数,需要开启-XX:+UnlockExperimentalVMOptions
,但依旧推荐在生产环境中使用。 -
年轻代中的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区一并回收。
G1垃圾收集分类
-
YoungGC
YoungGC并不是说现有的Eden区放满了就会马上触发,G1会计算下现在Eden区回收大概要多久时间,如果回收时间远远小于参数-XX:MaxGCPauseMills
设定的值,那么增加年轻代的region,继续给新对象存放,不会马上做YoungGC,直到下一次Eden区放满,G1计算回收时间接近参数-XX:MaxGCPauseMills
设定的值,那么就会触发Young GC -
MixedGC
不是FullGC,老年代的堆占有率达到参数-XX:InitiatingHeapOccupancyPercent
设定的值则触发,回收所有的Young和部分Old(根据期望的GC停顿时间确定old区垃圾收集的优先顺序)以及大对象区,正常情况G1的垃圾收集是先做MixedGC,主要使用复制算法,需要把各个region中存活的对象拷贝到别的region里去,拷贝过程中如果发现没有足够的空region能够承载拷贝对象就会触发一次Full GC -
Full GC
停止系统程序,然后采用单线程进行标记、清理和压缩整理,好空闲出来一批Region来供下一次MixedGC使用,这个过程是非常耗时的。(Shenandoah优化成多线程收集了)
G1 收集器的工作方式
-
图中主要展示了一个典型的 G1 GC 的 并发标记周期(Concurrent Mark Cycle) 的过程。
-
图中的水平箭头表示各线程(用户线程和GC线程)的执行路径。
-
竖直的
Safepoint
线表示一次GC过程中会暂停所有用户线程的时刻(Stop-The-World)。
1 | 1️⃣ 初始标记(Initial Mark) |
-
G1的GC 过程会经历多次
Safepoint
,但只有并发标记阶段
是整个周期中最耗时但不会暂停用户线程(STW)的部分。
Safepoint(安全点)
- 在 JVM(Java 虚拟机)中,Safepoint(安全点) 是一个非常重要的概念,它代表 所有线程必须“安全地”暂停的某个时间点,以便 JVM 能够执行某些全局操作,比如:
垃圾回收(GC)
栈遍历(例如生成堆快照、做逃逸分析等)
类卸载
JIT 编译的一些重写操作等 - 如果线程正在运行、改变堆中对象的数据,那么 GC 就无法准确标记和回收对象。因此 JVM 需要 让所有线程在一个“可控、安全的位置”上暂停,这个位置就叫 Safepoint。
- Safepoint 是怎么工作的?
1.JVM 发出“进入 Safepoint”的信号(比如要开始 GC)
2.所有线程收到信号后,必须等到“最近的 Safepoint”再停下来:
Safepoint 不是任意位置都可以停,它只出现在字节码中一些特定的指令点(比如方法调用、循环跳转等)
3.等所有线程都进入 Safepoint 后,JVM 才能开始执行 GC 等全局操作。
G1 收集器参数配置表
参数名 | 说明 | 默认值 |
---|---|---|
-XX:+UseG1GC |
使用 G1 垃圾收集器 | - |
-XX:ParallelGCThreads |
指定 GC 工作的线程数量 | 与 CPU 数量相关 |
-XX:G1HeapRegionSize |
设置堆分区大小(1MB~32MB,2 的幂),堆默认划分为 2048 个 Region | 自动计算 |
-XX:MaxGCPauseMillis |
设置 GC 目标最大暂停时间 | 200ms |
-XX:G1NewSizePercent |
新生代初始占比(占整个堆),实验性参数,-XX:+UnlockExperimentalVMOptions | 5% |
-XX:G1MaxNewSizePercent |
新生代最大占比(占整个堆),实验性参数,-XX:+UnlockExperimentalVMOptions | 60% |
-XX:TargetSurvivorRatio |
Survivor 区填充目标(超过该比例,晋升老年代) | 50% |
-XX:MaxTenuringThreshold |
最大对象年龄阈值(年龄超过将进入老年代) | 15 |
-XX:InitiatingHeapOccupancyPercent |
老年代的使用率超过该值触发 Mixed GC | 45% |
-XX:G1MixedGCLiveThresholdPercent |
Mixed GC 中:Region 存活对象占比低于该值才会被回收,实验性参数,-XX:+UnlockExperimentalVMOptions | 85% |
-XX:G1HeapWastePercent |
Mixed GC 回收目标:空闲 Region 达堆总量该百分比就结束混合回收 | 5% |
-XX:G1ReservePercent |
为满足停顿时间目标保留的堆空间比例,是一种空间换时间的策略,在内存不足时可能造成内存更加紧张 | 10% |
-XX:G1UseAdaptiveIHOP |
是否启用自适应IHOP,启用后G1会在初始采样后自动调整IHOP值 | true |
-XX:G1AdaptiveIHOPNumInitialSamples |
指定前几次GC活动按IHOP参数计算 | 3 |
-XX:SoftRefLRUPolicyMSPerMB |
每MB堆内存中软引用的过期时间(ms) | 1000 |
-XX:G1OldCSetRegionThresholdPercent |
设置一次混合GC中需要清理的Old区的内存比例,调大可降低G1频率但会增加每次GC时间 | 10% |
-XX:G1MixedGCCountTarget |
设置G1垃圾回收器的线程上限,HotSpot会根据清理目标自动计算所需线程数,但不会超过此上限 | 8 |
ZGC 垃圾回收器
-
ZGC(Z Garbage Collector)是一款JDK 11中新加入的具有实验性质的低延迟垃圾收集器,ZGC可以说源自于是Azul System公司开发的C4(Concurrent Continuously Compacting Collector) 收集器
Platform | Supported | Since | Comment |
---|---|---|---|
Linux/x64 | ✅ | JDK 11 | |
Linux/AArch64 | ✅ | JDK 13 | |
macOS | ✅ | JDK 14 | |
Windows | ✅ | JDK 14 | Requires Windows version 1803 (Windows 10 or Windows Server 2019) or later. |
-
ZGC收集器是一款基于Region内存布局的,暂时不设分代的(jdk21后支持分代),使用了
读屏障
、颜色指针
等技术来实现可并发的标记-整理算法的,以低延迟为首要目标的一款垃圾收集器。
颜色指针
- 在 ZGC(Z Garbage Collector)中,颜色指针(Colored Pointer)是一种核心机制,把对象的 GC 状态信息直接嵌入到对象的引用(地址)中,用于在不增加对象额外元数据的前提下,跟踪对象在垃圾回收过程中的状态。
- 在传统 GC 中,对象的“颜色”(如白色、灰色、黑色)表示其在 GC 不同阶段的状态,这些信息一般存储在额外的数据结构中(如记忆集合、位图等)。
- 而在 ZGC 中,ZGC 将对象的这些状态信息编码进指针的高位中。所以一个对象引用(pointer)不仅仅是内存地址,还携带了对象在 GC 过程中的“颜色”信息。
- ZGC 主要运行在 64 位系统上,但当前的操作系统和 CPU 实际上只使用 48~57 位虚拟地址。ZGC 利用未使用的高位进行编码。
- 每个对象有一个64位指针,这64位被分为:
位数 | 名称 | 含义 |
---|---|---|
18 位 | 预留 | 保留位,未来可能用于扩展功能 |
1 位 | Finalizable | 表示对象可终结(即实现了 finalize() 方法) |
1 位 | Remapped | 标识对象是否已被转移(用于标识某个引用是否已经被重定向(更新为新地址));为 1 表示该对象未在重定位集(Relocation Set)中,即新地址,为0表示旧地址 |
1 位 | Marked1 | 标记位之一,用于 GC 过程中的对象标记阶段 ,由于 ZGC 是并发 GC,需要两位来支持颜色切换机制(Color Flip),确保在不同 GC 周期中可以区分新旧标记,需要触发一次读取屏障(load barrier),找到对象的新位置,并更新这个引用。 |
1 位 | Marked0 | 另一个标记位,与 Marked1 配合用于 GC 标记阶段 |
42 位 | 对象地址部分 | 实际的内存地址部分,最多可表示 2^42 字节(即 4 TB),jdk15后占用 44位,支持 16TB |
读屏障
-
ZGC(Z Garbage Collector)中的**读屏障(Read Barrier)是其核心机制之一,它保证了并发压缩(对象移动)**时程序的正确性,是实现低延迟、高并发垃圾回收的关键。
-
在传统 GC 中,如果对象在 GC 过程中被移动,程序访问到的对象地址可能就无效了。因此,需要**“停世界”**将所有引用更新。
-
而 ZGC 的设计目标是 <10ms 的 GC 停顿时间,所以它采用
并发标记 + 并发移动 + 并发引用更新
的模式。在这种模式下,对象可以在程序运行时被移动,但程序访问时必须“知道”这个对象是否已经被移动,并获取其最新地址,这就是读屏障的职责。 -
读屏障是一种在读取对象引用时自动插入的逻辑,用于检查引用是否有效,并在必要时进行修正(即地址重定向)。
-
ZGC 是目前唯一在所有对象访问中都使用读屏障的 GC,它在每次对象指针解引用时都进行如下操作:
职责 | 说明 |
---|---|
检测引用是否是老地址 | 利用指针中的元信息(颜色指针,如 Remapped 位)判断引用是否已经被更新 |
如果未更新则重定向引用 | 如果引用的是旧地址,屏障会通过 forwarding table 找到新地址并更新 |
保证最终访问对象的地址正确 | 即使在对象移动过程中,程序也总能访问到有效地址,无需停顿所有线程 |
graph TD
A[程序访问对象引用<br/>例如:Person p = personField] --> B[JVM 插入读屏障逻辑]
B --> C[读屏障检查颜色指针中的 Remapped 位]
C --> D{Remapped 位为 0 吗?}
D -- 是 --> E[检查对象是否已被移动]
E --> F[更新引用地址为新地址]
F --> G[设置 Remapped 位为 1]
G --> H[使用更新后的引用访问对象]
D -- 否 --> I[直接使用当前引用访问对象]
-
ZGC的Region可以具有大、中、小三类容量:
- 小型Region(Small Region) : 容量固定为2MB,用于放置小于256KB的小对象。
- 中型Region(Medium Region) : 容量固定为32MB,用于放置大于等于256KB但小于4MB的对象。
- 大型Region(Large Region) : 容量不固定,可以动态变化,但必须为2MB的整数倍,用于放置4MB或以上的大对象。
每个大型Region中只会存放一个大对象,这也预示着虽然名字叫作“大型Region”,但它的实际容量完全有可能小于中型Region,最小容量可低至4MB。
-
ZGC 特点:
- 支持极大堆(JDK 15 起支持最高 16TB)
- GC 停顿时间通常低于 1ms(与堆大小基本无关)
- 适合低延迟、高吞吐、高可用的应用场景,如交易系统、广告推荐等
ZGC 收集器的工作方式
1 | 1️⃣ 并发标记(Concurrent Mark): |
ZGC存在的问题
-
ZGC最大的问题是
浮动垃圾
。ZGC的停顿时间是在10ms以下,但是ZGC的执行时间还是远远大于这个时间的。假如ZGC全过程需要执行10分钟,在这个期间由于对象分配速率很高,将创建大量的新对象,这些对象很难进入当次GC,所以只能在下次GC的时候进行回收,这些只能等到下次GC才能回收的对象就是浮动垃圾。 -
ZGC没有分代概念,每次都需要进行全堆扫描,导致一些“朝生夕死”的对象没能及时的被回收。
-
目前唯一的办法是增大堆的容量,使得程序得到更多的喘息时间,但是这个也是一个治标不治本的方案。如果需要从根本上解决这个问题,还是需要引入分代收集,让新生对象都在一个专门的区域中创建,然后专门针对这个区域进行更频繁、更快的收集。
-
JDK21正式引入了分代(jdk17是预览版),可以在生产环境中使用,需要手动开启:
-XX:+ZGenerational
ZGC 收集器参数配置表
参数名 | 说明 | 默认值(如未特殊标注) |
---|---|---|
-XX:+UseZGC |
启用 ZGC 垃圾收集器 | - |
-XX:+UnlockExperimentalVMOptions |
解锁实验性参数(JDK 11~14 使用 ZGC 必须) | - |
-XX:+UseLargePages |
启用大页内存(性能优化) | false |
-XX:+UseTransparentHugePages |
启用透明大页(部分 Linux 上可结合使用) | false |
-Xmx / -Xms |
设置最大/初始堆大小 | 用户配置 |
-XX:ZUncommitDelay=<秒> |
未使用内存释放回操作系统的延迟时间(ZGC 会自动释放内存) | 300(5分钟) |
-XX:SoftMaxHeapSize=<大小> |
软最大堆(Soft Heap Limit):ZGC 尝试将使用的堆控制在该值以内 | 默认等于 -Xmx |
-XX:MaxHeapFreeRatio |
最大堆空闲比例(超过此比例可能触发内存释放) | 70 |
-XX:MinHeapFreeRatio |
最小堆空闲比例(低于此比例可能触发扩容) | 40 |
-XX:+ZGenerational |
启用 ZGC 分代收集(从 JDK 21 起支持,默认关闭) | false(JDK 21+) |
-XX:+PrintGC / -Xlog:gc* |
开启 GC 日志输出 | - |
-XX:+ZProactive |
主动回收策略(在系统空闲时尝试回收) | true |
-XX:ZCollectionInterval=<秒> |
主动回收之间的最小时间间隔(配合 ZProactive) | 默认值因版本而异 |
ZGC vs G1 GC vs Parallel GC 对比表
特性 | ZGC | G1 GC | Parallel GC |
---|---|---|---|
设计目标 | 极低延迟,<1ms 停顿 | 平衡 低延迟 与 高吞吐 | 高吞吐,最大化 CPU 使用 |
GC 停顿时间 | <1ms,堆大小增加不影响停顿时间 | 几十到几百毫秒,堆越大停顿越明显 | 停顿时间可能达到秒级,堆越大越明显 |
堆大小支持 | 支持高达 16TB(JDK 15+) | 支持 最多 4TB | 支持大堆,但停顿明显 |
是否分代 | 默认不分代(JDK 21+ 可开启 -XX:+ZGenerational ) |
分代(新生代 + 老年代) | 分代(新生代 + 老年代) |
并发回收 | 是,包括标记、压缩、引用处理都并发 | 部分并发,仍包含明显 Stop-The-World 阶段 | 否,全部 Stop-The-World |
移动对象时是否停顿 | 否,使用读屏障并发转移对象 | 是(通过复制区域) | 是 |
实现机制 | 标记-重定位,基于读屏障 | Region 分区 + 标记-复制 + Mixed GC | 标记-复制(新生代)、标记-整理(老年代) |
吞吐能力 | 中高 | 高 | 最高 |
适用场景 | 低延迟系统,如金融、交易、推荐等实时应用 | 通用后台系统、Web 服务等 | 批处理、数据计算、日志分析等不敏感于停顿的系统 |
默认启用 | 否,需指定 -XX:+UseZGC |
JDK 9+ 默认 GC | JDK 8 及以前默认 GC |
调优复杂度 | 低,大多数参数可省略 | 中,需要设置目标停顿时间等 | 高,需要精细配置 Eden/Survivor 等比例 |
GC 日志配置方式 | 统一日志格式:-Xlog:gc* (JDK 9+) |
支持传统的 -XX:+PrintGCDetails 和 -Xlog:gc* 日志输出 |
主要使用旧参数:-XX:+PrintGCDetails 等 |
ZGC 吞吐能力较弱的原因
原因 | 解释 |
---|---|
1. 并发阶段代价较高 | ZGC 几乎所有的 GC 工作(包括标记、整理、引用处理、转移对象)都在与应用线程并发执行。虽然减少了停顿,但这些 GC 线程与业务线程共享 CPU 资源,增加了 CPU 上下文切换和缓存竞争,从而影响业务线程的执行效率。 |
2. 读屏障开销 | ZGC 依赖着色指针和读屏障(load barrier)机制来跟踪对象引用状态,支持并发转移。虽然非常高效,但仍比传统 GC 的普通读写路径慢一些,在高频访问对象场景下会带来一定 CPU 开销。 |
3. 对硬件依赖高,调度保守 | 为了实现“<1ms 停顿”的目标,ZGC 会选择更保守的调度策略(如避免并发线程使用过多 CPU),而不会像 Parallel GC 那样“榨干”所有核心资源。 |
4. 内存开销更高 | 为了支持并发压缩和转移,ZGC 通常需要为每个对象保留元数据和更多的转移空间(即“浮动垃圾”区域),这可能导致频繁的 GC 周期,影响吞吐。 |
5. 设计目标非吞吐优先 | ZGC 的首要目标是低延迟而非最大吞吐。与 Parallel GC(以吞吐为核心)设计目标不同,ZGC 更适合场景为“对延迟敏感但吞吐可接受”的系统。 |
最糟糕的情况下吞吐量会降低15%。这都不是事,停顿时间足够优秀。至于吞吐量,通过扩容分分钟解决。
另外,Oracle官方提到了它最大的优点是:它的停顿时间不会随着堆的增大而增长!
也就是说,几十G堆的停顿时间是10ms以下,几百G甚至上T堆的停顿时间也是10ms以下。
ZGC 吞吐弱,不是因为技术落后,而是因为它主动选择在低延迟和高吞吐之间偏向了低延迟。
在对响应时间要求极高的系统中,它是非常合适的选择。
但如果你的目标是压榨机器性能跑批处理、日志分析这类吞吐导向型任务,Parallel GC 或 G1 会更合适。
内存溢出(OutOfMemoryError,简称 OOM)
-
JVM 常见内存溢出类型汇总表
溢出类型 | 异常信息 | 触发原因/描述 | 相关参数和建议配置 |
---|---|---|---|
堆内存溢出 | java.lang.OutOfMemoryError: Java heap space |
- 创建大量对象,堆空间不足 - 内存泄漏:对象不再使用却有强引用 - 老年代对象太多无法晋升 |
-Xms / -Xmx 设置堆大小 |
栈溢出(递归) | java.lang.StackOverflowError |
- 方法无限递归或递归层级太深 | -Xss 设置线程栈大小 |
无法创建新线程 | java.lang.OutOfMemoryError: unable to create new native thread |
- 创建线程过多(如线程池配置过大) - 系统或 JVM native 线程资源耗尽 |
控制线程池大小,避免无限新建线程 |
元空间溢出 | java.lang.OutOfMemoryError: Metaspace |
- 动态加载类过多(如使用 CGLIB、JSP 动态生成类) - 类无法卸载(如 ClassLoader 泄漏) |
-XX:MetaspaceSize / -XX:MaxMetaspaceSize |
直接内存溢出 | java.lang.OutOfMemoryError: Direct buffer memory |
- 使用 ByteBuffer.allocateDirect() 分配大量直接内存- Netty 等框架默认使用直接内存 |
-XX:MaxDirectMemorySize |
GC 开销过高 | java.lang.OutOfMemoryError: GC overhead limit exceeded |
- JVM 花费 >98% 的时间 GC 但回收 <2% 的内存,认为进入“GC 死循环” | 分析 GC 日志、优化堆设置 |
类卸载失败(内存泄漏) | (不一定报错,但可能导致 Metaspace OOM) | - Web 容器频繁部署热更新 WAR 包时,ClassLoader 无法卸载,导致类永久驻留 | 优化 ClassLoader 管理,使用内存分析工具 |
-
堆快照(堆转储)文件分析
参数 | 说明 |
---|---|
-XX:+HeapDumpOnOutOfMemoryError |
OOM 时生成堆转储文件 ,建议生产环境开启 |
-XX:HeapDumpPath=xxx.hprof |
指定堆转储文件保存路径 默认保存在当前目录 |
-
常用分析工具与命令
工具/命令 | 简介与功能 | 使用说明 |
---|---|---|
MAT(Memory Analyzer Tool) | Eclipse 出品的强大图形工具,支持泄漏分析、对象引用链分析、内存占用统计等 | 下载地址: https://www.eclipse.org/mat/ 打开后直接导入 .hprof 文件即可分析 |
VisualVM | 官方可视化 JVM 监控工具,支持实时分析、GC 查看、线程状态与堆转储查看 | 附带于 JDK(或单独安装),打开 .hprof 文件进行可视化分析 |
JProfiler | 商业级 Java 性能分析工具,提供堆分析、CPU 分析、线程分析等全套功能 | 支持打开 .hprof 文件,也可在运行时配合使用(需付费或试用) |
YourKit | 商业性能分析工具,界面友好,支持丰富的分析功能 | 支持堆分析,适合内存泄漏排查 |
jhat(过时) | JDK 附带的旧工具,用于分析 .hprof 文件,开启 Web 界面查看(已废弃) |
命令:jhat heapdump.hprof ,浏览器访问 http://localhost:7000 (JDK 8 及以下) |
jmap | 用于生成堆转储文件或查看堆对象统计信息(不是分析工具本身) | 命令:jmap -dump:format=b,file=heap.hprof <pid> 生成转储;配合 MAT 使用 |
jcmd | 更现代的诊断命令工具,可生成 heap dump、执行 GC、打印 VM 状态等 | 命令:jcmd <pid> GC.heap_dump heap.hprof |
GCEasy.io | 在线分析工具,支持 .hprof 文件和 GC 日志上传分析 |
网站:https://gceasy.io |
-
GC日志相关参数(JDK1.8及以下)
参数 | 说明 |
---|---|
-XX:+PrintGC |
打印基本 GC 信息(建议配合 -XX:+PrintGCDetails 使用),与-verbose:gc 等价 |
-XX:+PrintGCDetails |
打印详细的 GC 日志信息(如各区域使用情况、对象分配、晋升等) |
-XX:+PrintGCDateStamps |
在 GC 日志中添加日期时间戳 |
-XX:+PrintGCTimeStamps |
在 GC 日志中添加 JVM 启动以来的时间戳 |
-Xloggc:/var/log/myapp/gc.log |
将 GC 日志输出到指定文件 |
-XX:+UseGCLogFileRotation |
启用 GC 日志文件轮转(适用于大规模系统的 GC 日志管理) |
-XX:NumberOfGCLogFiles=5 |
最多保留 5 个 GC 日志历史文件 |
-XX:GCLogFileSize=10M |
每个 GC 日志文件最大为 10MB |
-XX:+PrintGCCause |
打印 GC 的触发原因(如 Minor GC、System.gc() 等) |
-
GC日志相关参数(JDK9+)
-Xlog
是用于配置 JVM 日志记录的参数,它是从 JDK 9 开始引入的统一日志框架(Unified Logging)的一部分。- 通过 -Xlog 选项,你可以控制日志的 标签(tags)、输出级别(level)、输出位置(output) 等内容。
- 基本格式
1
-Xlog[:[tag1[,tag2...]][[:level][:[output][:[decorators][:rotation]]]]]
- 常见 Tags(标签)
类别 标签 含义 用途示例 类加载 classload
JVM 类加载活动的总体信息 观察类加载失败或重复加载 class+load
显示每个类加载过程细节 定位类加载异常、热加载分析 class+unload
记录类卸载事件 分析类卸载时机或内存释放情况 垃圾回收 gc
总体 GC 活动信息 GC 调优入门使用 gc+start
GC 事件开始时间与类型 查看 GC 频率与触发点 gc+heap
GC 前后的堆使用情况 分析堆使用与分区占比 gc+phases
GC 内部阶段细节 G1/ZGC 等调试用 gc+age
年龄分布情况 判断对象晋升路径、老年代压力 gc+ergo
GC 的自适应调整决策 查看线程、堆大小自动调整逻辑 JIT 编译 jit
JIT 编译日志入口 性能热点方法分析 compiler
显示方法编译、时间等 判断是否有方法未被优化 codecache
已编译代码存储区使用情况 判断是否达到缓存上限 nmethod
JIT 生成的 native 方法生命周期 观察 native 代码生成与卸载 内存 memory
总体内存使用情况 诊断内存泄漏或溢出 gc+heap
堆结构与占用情况 与 GC 联合分析使用 os+memory
JVM 与操作系统间的内存交互 判断是否系统层内存申请失败 线程 thread
线程的创建、终止、状态变化 线程泄漏或频繁创建排查 safepoint
JVM 进入/退出 safepoint 的信息 分析 STW 停顿时间与频率 锁与同步 synchronization
锁竞争、获取和释放信息 分析性能瓶颈中的锁争用 monitorinflation
轻量级锁升级为重量级锁过程 排查锁膨胀导致的延迟问题 操作系统交互 os
JVM 与操作系统交互日志 常规系统资源请求信息 os+thread
OS 层级线程管理信息 高并发时线程绑定与调度分析 os+cpu
CPU 使用与分布情况 排查高 CPU 使用的问题 类元数据 metaspace
元空间内存使用与变化 排查 Metaspace OOM cds
类共享数据(CDS)日志 分析类加载加速是否生效 安全 security
安全相关操作日志 权限检查失败、密钥加载等调试 module
模块系统(JPMS)相关日志 模块访问控制失败分析 其他 start
JVM 启动流程日志 启动阶段慢、出错时使用 init
各子系统初始化过程 定位子系统启动顺序与异常 jni
Java 调用 native 方法日志 JNI 崩溃、性能问题分析 classpath
类路径加载详情 类找不到、路径冲突等问题排查 exceptions
异常信息及堆栈打印 捕获未处理异常、频繁抛错分析 -
Level(级别)
等级 含义说明 用途示例 off
关闭日志记录 完全禁用指定标签的日志输出 error
严重错误,仅记录影响系统运行的错误信息 捕获崩溃、严重故障 warning
潜在问题,可能影响稳定性 记录可能的内存、配置问题 info
常规信息(默认级别) 日常运行日志,如 GC 次数 debug
更详细的调试信息,适用于问题诊断 跟踪行为变化或代码路径 trace
最详细的信息,记录几乎所有细节 深度调试,如方法级别跟踪 all
打印所有级别的日志(包括 trace 及以上) 全量日志输出,用于完全监控 -
Output(输出位置)
输出位置 含义说明 示例用法 stdout
标准输出(默认) -Xlog:gc=info:stdout
stderr
标准错误输出 -Xlog:gc=info:stderr
file=路径
输出到指定文件路径(自动创建文件) -Xlog:gc:file=gc.log
多个输出用逗号分隔 可同时输出到多个位置 -Xlog:gc:file=gc.log,stdout
-
Decorators(装饰器)
装饰器 含义说明 示例输出片段(含该装饰器时) time
显示当前系统时间(wall-clock time) 2025-05-16T10:45:12.123+0800
uptime
显示 JVM 启动以来的运行时间(毫秒) 5.123s
(表示已运行 5.123 秒)level
显示日志级别(如 info, warning 等) [info]
,[debug]
tags
显示日志标签 [gc]
,[class,load]
tid
显示线程 ID tid=0x00007fddc4012800
hostname
显示主机名 host=example.local
pid
显示当前 JVM 进程 ID pid=12345
-
Rotation (日志轮转设置)
设置参数 含义说明 示例值 filecount=n
保留的日志文件数量(最大文件轮转数) filecount=5
filesize=n
单个日志文件的最大大小(支持单位:k/m/g) filesize=10m
-
示例:
1
-Xlog:gc*:file=/var/log/app/gc.log:time,uptime,level,tags:filecount=5,filesize=20M
项 值 含义 tag
gc*
匹配所有以 gc
开头的标签(如gc+heap
等)level
省略 默认是 info
output
file=/var/log/app/gc.log
日志写入该文件 decorators
time,uptime,level,tags
日志前缀包含时间、启动时间、日志级别、标签 rotation
filecount=5,filesize=20M
最多保留 5 个日志文件,单个文件最大 20MB -
日志文件中可以使用变量
1
-Xlog:gc*:file=gc_%p_%t.log
常用变量表
变量 含义 %p 进程ID(Process ID) %t 时间戳(代表日志文件创建时的时间戳) %h 主机名(Hostname) %n 序列号,从0开始,每创建新文件加1 %u 唯一标识符,用于解决文件名冲突 %i 当多个JVM进程使用相同的文件名模式时的区分计数器 时间相关变量表
变量 含义 %Y 年(Year) %m 月(Month) %d 日(Day) %H 小时(Hour) %M 分钟(Minute) %S 秒(Second) 1
2
3
4
5
6
7
8# 基础配置
-Xlog:gc*:file=gc_%p_%Y%m%d_%H%M%S.log:time,uptime,level,tags:filecount=5,filesize=20M
# 详细GC日志配置
-Xlog:gc*=debug:file=gc_%p_%t.log:time,uptime,level,tags:filecount=10,filesize=50M
# 多目标输出
-Xlog:gc*=info:file=gc.log::filecount=5,filesize=20M -Xlog:gc*=debug:file=gc_detailed.log -
对于GC日志的分析
人工分析还是太复杂了,可以使用GCEasy.io,每个月免费5次。