当前位置:首页 > 文章列表 > 文章 > java教程 > Java GC 日志怎样定位暂停时间来源

Java GC 日志怎样定位暂停时间来源

来源:17golang原创 2026-10-14 13:15:18 0浏览 收藏

定位 Java 暂停来源,先找到对应时间窗口中的 Pause 摘要,再关联同一 GC(n) 的阶段明细和 CPU 时间;同时检查安全点记录,分清线程到达安全点的等待与真正停留时间。不能把一个并发标记周期的全部耗时当成应用停止时间,也不能凭“发生过 GC”就认定接口变慢由 GC 引起。

以下示例采用 JDK 25 HotSpot/G1 的统一日志。其他 JVM、收集器和 JDK 版本应以自己的标签、字段和官方文档为准。官方调优入口:https://docs.oracle.com/en/java/javase/25/gctuning/

从有界日志配置开始,不先堆调优参数

统一日志能够把 GC 类型、阶段和安全点记录放在同一观察体系中。它的价值是帮助开发者与运维区分候选原因,不是自动产出根因,更不是采用了某种收集器就能消除暂停。没有本项目的数据,不应声称某一配置会让响应时间下降多少。

在可控发布环境中,为已有应用准备可写日志目录,并把下面的日志选项加入 JVM 启动参数。app.jar 是读者自己的应用包,不是本文提供的程序:

# 相对目录由部署配置提供;日志文件按进程与启动时间区分。
java '-Xlog:gc*=info,safepoint=info:file=logs/gc-%p-%t.log:time,uptime,level,tags:filecount=5,filesize=20M' -jar app.jar

gc* 包含带 GC 标签的多种标签组合;没有星号的匹配范围不同。time 帮助对齐业务记录,uptime 表示 JVM 启动后的时间,%p 与 %t 用于区别进程和启动批次。轮转大小是目标值,不是磁盘占用的严格上限,容量还受当前活动文件等因素影响。

短时诊断需要阶段细节时,可以在受控启动配置中使用下面的 JVM 参数片段:

# 仅为 JVM 参数片段,应放在 -jar 之前;不是独立 shell 命令。
-Xlog:gc*=debug,safepoint=info:file=logs/gc-detail-%p-%t.log:time,uptime,level,tags:filecount=5,filesize=20M

标签组合的可用性可以通过目标 JVM 的 -Xlog:help 了解。本文不在本地启动 JVM、压测应用或采集真实日志;配置是供读者自行采用的例子。

GC 编号关联一次事件,时间关联业务窗口

一组诊断记录包含六个不同对象:时间标记、GC 编号、暂停摘要、阶段明细、CPU 时间和安全点记录。时间标记与 GC 编号属于关联边界;摘要、阶段、CPU 时间属于 GC 记录;安全点记录属于 JVM 停顿记录。

GC 日志事件关联字段与安全点记录的静态结构图
GC 编号关联同一次 GC 的摘要与明细,时间标记用于对齐安全点和业务窗口。静态连线不表示所有记录有同一种计时口径。

先固定进程、JVM 启动批次和时间窗口,再看 GC 编号。同一个数字在另一 JVM 中没有关联意义。多个实例的日志合并后,如果丢失进程身份,容易把甲实例的暂停与乙实例的慢请求配到一起。

可以在读者自己的日志文件中初步查找关键词,保留匹配行前后的上下文:

# 替换为同一次 JVM 启动产生的日志文件;此命令只筛出候选行。
rg 'Pause|Concurrent|Safepoint|User=|Real=' logs/gc-demo.log

筛选结果不代替完整事件。尤其阶段明细可能没有 Pause 字样,只筛摘要会漏掉工作量分解。跨文件轮转时还需要保留相邻文件,而不是只看最后一个片段。

Pause 是停顿摘要,Concurrent 不是同义词

下面只用两条人为编写的教学记录说明字段,不代表实测性能或完整事件序列:

[18.420s][info][gc] GC(42) Pause Young (Normal) (G1 Evacuation Pause) 384M->96M(768M) 14.200ms
[19.100s][info][gc] GC(43) Concurrent Mark Cycle 218.500ms

第一行的 14.200ms 是这条暂停摘要报告的耗时。384M->96M(768M) 是堆占用前后及堆容量信息,不是耗时分解,更不是对象分配栈。第二行标识并发周期,不能把 218.500ms 直接加进应用停止时间。

G1 的并发标记相关工作中仍存在 Remark、Cleanup 等停顿部分,应分别读取相应 Pause 记录。Mixed 表示回收工作涉及年轻代及选定老年代区域,并不等于 Full GC。看到 Full GC 则需要另查触发原因和此前的回收状况,不宜只按耗时大小命名事件。

阶段和 CPU 时间提示方向,不直接给出代码根因

阶段明细可以帮助区分复制对象、处理堆根、引用处理等工作的耗时。对并行阶段,线程耗时的合计、均值与暂停墙钟时间不是一个口径;多个子阶段还可能有包含关系。不要把所有明细数字相加,声称得到了准确暂停时间。

User、Sys、Real 分别提供用户态、系统态与实际经过时间的线索。并行工作使累计 CPU 时间可能高于墙钟时间;如果实际经过时间明显大于累计 CPU 时间,则需要结合 CPU 调度、容器配额或主机负载继续排查。系统态耗时异常也应联系内存和 I/O 环境判断。

这些只是排查方向。日志没有指出某个业务方法分配了对象时,就不能凭一次 GC 记录归因到该方法。还需适当的分配分析或应用层观测,本篇不把额外工具的结果虚构为已经取得的证据。

安全点等待与 GC 工作属于不同边界

JDK 25 的安全点日志分别报告 到达安全点、安全点内工作、离开安全点与安全点总时间;将这些记录与GC 暂停、业务延迟对齐后,才能区分计时边界。这六个节点分别属于 JVM 计时、GC 计时与应用计时。

安全点各项时间与 GC 暂停和业务延迟的静态边界图
安全点总时间包含到达、停留和离开部分,GC 暂停与业务延迟还需按事件与时间对齐。图中连线表示包含或对照,不表示简单相加即可得到请求耗时。

Reaching safepoint 较高时,优先查看同步到安全点的开销,不能把整段都算作 GC 复制对象。At safepoint 也可能对应其他 VM 操作,并非每个安全点都是 GC;应同时读取操作名称。这里特别采用 JDK 25 的字段,旧版日志可能另外显示清理项,不能直接套用一份解析模板。

对同一 GC,安全点时间与暂停摘要可能覆盖重叠区间。因此,它们用于解释口径,不应直接累计成两段独立暂停。请求还可能包含排队、锁等待、数据库或网络耗时;时间重叠是归因线索,不是充分证明。

分配压力要看连续样本和回收后的占用

频繁年轻代回收可以提示分配压力,但仅凭堆占用下降幅度,不能准确推算所有对象的分配速率。对比时至少保留相似负载窗口、回收间隔、回收后占用、老年代变化和大对象区域信息;请求量变化后,原始 GC 次数自然也可能变化。

观察信号进一步区分不能直接认定
年轻代暂停频繁请求增长、分配量与存活量一定存在内存泄漏
回收后占用持续升高存活对象、缓存增长与负载变化只需把堆调大
Full GC 或疏散失败触发原因、老年代压力和连续历史单个参数可以根治
Real 时间异常偏高CPU 调度与环境负载GC 算法本身必然变慢
业务尾延迟高而无对应长暂停其他等待及观测覆盖范围日志没看到就绝对无 GC 影响

采用日志诊断的收益是缩小候选原因;风险是把相关性当成因果、把采集开销算成原有问题。详细日志增加磁盘写入,采集应限时、可轮转、受权限保护,并保留完整上下文。不要在没有证据时同时改堆大小、年轻代和多项 GC 参数,最后无法判断哪项变化起作用。

用哪些指标判断后续调整

在同等请求量和资源配额下,比较暂停次数、总暂停时间、长尾暂停、回收后占用趋势与应用尾延迟;还要观察吞吐及错误率是否受到影响。G1 的暂停目标不是每次暂停的硬性保证,单条日志低于目标也不能代表整个服务达到延迟要求。

官方命令说明:https://docs.oracle.com/en/java/javase/25/docs/specs/man/java.html
官方 G1 说明:https://docs.oracle.com/en/java/javase/25/gctuning/garbage-first-g1-garbage-collector1.html
官方 G1 调优资料:https://docs.oracle.com/en/java/javase/25/gctuning/garbage-first-garbage-collector-tuning.html

两个相关问题

只统计含 GC 字样的行能得到暂停总量吗?不能。并发记录、阶段明细和暂停摘要可能同时存在,先定义统计口径,避免重复计数。

GC 时间短,业务就不会慢吗?不是。并发回收可能争用资源,业务还存在非 GC 等待。定位暂停来源是性能诊断的一部分,不是对全部业务延迟的解释。

读 GC 日志的关键不是找最大数字,而是先确定数字属于哪一次事件、哪种时间边界,再结合完整窗口判断。先有证据,再选择调整方向。

版本声明
本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
Java JUnit 5 参数化测试怎样组织输入Java JUnit 5 参数化测试怎样组织输入
上一篇
Java JUnit 5 参数化测试怎样组织输入
Java 线程池队列过长怎样设置背压
下一篇
Java 线程池队列过长怎样设置背压
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之JavaScript设计模式
    前端进阶之JavaScript设计模式
    设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
    543次学习
  • GO语言核心编程课程
    GO语言核心编程课程
    本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
    516次学习
  • 简单聊聊mysql8与网络通信
    简单聊聊mysql8与网络通信
    如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
    500次学习
  • JavaScript正则表达式基础与实战
    JavaScript正则表达式基础与实战
    在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
    487次学习
  • 从零制作响应式网站—Grid布局
    从零制作响应式网站—Grid布局
    本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
    485次学习
查看更多
AI推荐
  • PubMedQA数据集详解:生物医学问答基准、功能与应用指南
    PubMedQA
    深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
    469次使用
  • H2O EvalGPT:开源LLM大模型评估与排行榜工具
    H2O EvalGPT
    H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
    530次使用
  • LMArena是什么?伯克利AI模型评估平台使用指南与功能解析
    LMArena
    LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
    549次使用
  • 斯坦福HELM:大语言模型Holistic Evaluation整体评估框架详解
    HELM
    深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
    499次使用
  • MMBench详解:多模态大模型基准测试、功能特点与使用指南
    MMBench
    MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
    317次使用