Java 运维诊断指南:JVM 参数配置与信息收集

总结摘要
Java 运维诊断指南:JVM 参数配置与信息收集

Java 运维诊断实践指南:从零命令依赖到高效问题定位

一、背景与目标

1.1 为什么需要这篇指南

在生产环境中,Java 应用的问题诊断往往面临两难:

  • 公司内网环境:安全合规要求严格,无法直接执行 jstackjcmd 等诊断命令
  • 个人项目环境:可以自由使用命令,但缺乏系统化的诊断方法论

本指南旨在帮助中高级 Java 开发工程师建立一套不依赖命令的运维诊断体系,同时保留个人项目的灵活诊断能力

1.2 适用读者

  • 负责 Java 应用生产环境运维的开发者
  • 需要快速定位 OOM、CPU 飙高、GC 频繁等问题的工程师
  • 希望在受限环境下仍能高效诊断的技术人员

1.3 核心思路

1
2
3
事前预防 → 事中自动采集 → 事后分析
   ↓            ↓            ↓
合理配置    JVM参数自动导出  专业工具分析

关键原则

  • 所有诊断数据通过 JVM 启动参数自动导出,不依赖运行时命令
  • 充分利用公司可观测性平台获取数据
  • 建立标准化的问题排查流程

二、核心诊断数据及其作用

在开始配置之前,需要理解各类诊断数据的作用:

数据类型文件格式解决的问题分析工具
GC 日志文本GC 频率、停顿时间、内存区域变化GCEasy、GCViewer、Kibana
堆转储HPROF内存泄漏、大对象、引用链MAT、VisualVM、JProfiler
JFR 记录JFR完整 JVM 行为(GC、锁、IO、线程)JDK Mission Control
线程快照文本死锁、线程阻塞、CPU 热点文本分析、FastThread

2.1 各数据的使用场景

1
2
3
4
5
6
问题现象 → 数据选择 → 分析工具 → 定位根因

CPU 100%      → 线程快照 + JFR → FastThread/JMC → 找出占用 CPU 的线程栈
内存持续增长  → 堆转储        → MAT           → 分析大对象和引用链
应用频繁停顿  → GC 日志 + JFR → GCViewer/JMC  → 分析 GC 停顿时间和频率
接口响应变慢  → JFR           → JMC           → 分析锁竞争、IO 等待、线程调度

三、JDK 版本特性与选型建议

3.1 JDK 8/11/21 核心特性对比

特性JDK 8JDK 11JDK 17/21
默认 GCParallel GCG1G1(JDK 21 仍为 G1)
GC 日志格式旧版(-Xloggc:统一日志(-Xlog:统一日志(-Xlog:
容器感知需显式开启(8u191+)默认开启默认开启
JFR需解锁或特定版本完全免费开源完全免费开源
ZGC不支持实验性(JDK 11)生产可用(JDK 15+)
虚拟线程不支持不支持JDK 21 正式引入

3.2 版本选择建议

场景推荐版本原因
新项目JDK 17 或 21LTS 版本,G1 稳定,JFR 完整支持
已有 JDK 8 项目升级至 8u262+(OpenJDK)获得 JFR 免费支持,容器感知改善
低延迟、大堆场景JDK 21 + 分代 ZGC实验性(JDK 21 需 -XX:+ZGenerational),需充分测试
虚拟线程场景JDK 21+虚拟线程正式引入,需配套监控调整

3.3 各版本默认 GC 详细说明

JDK 版本默认 GC说明
JDK 8Parallel GC(并行回收器)吞吐量优先,适合计算密集型
JDK 9-20G1平衡吞吐量和延迟,适合大多数场景
JDK 21G1仍以 G1 为默认,分代 ZGC 为实验特性(需显式开启)

特别注意:JDK 21 并未将 ZGC 设为默认,我之前的表述可能引起误解,此处已修正。


四、零命令依赖:生产环境必配的 JVM 参数

4.1 JDK 8 完整参数模板(OpenJDK 8u262+)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
#!/bin/bash
JAVA_OPTS="-Xms4g -Xmx4g \

  # ========== 垃圾回收配置 ==========
  -XX:+UseG1GC \                    # 启用 G1 回收器(JDK 8 默认是 Parallel GC)
  -XX:MaxGCPauseMillis=200 \        # 目标最大 GC 停顿时间(毫秒),G1 会尽力达成
  -XX:G1HeapRegionSize=16m \        # G1 区域大小(可选,默认自动计算)
  -XX:InitiatingHeapOccupancyPercent=45 \  # 触发并发标记的堆占用百分比(默认 45)
  -XX:G1ReservePercent=10 \          # 预留空间百分比,防止晋升失败(默认 10)
  
  # ========== 容器感知(JDK 8u191+)==========
  -XX:+UseContainerSupport \         # 启用容器支持,识别 Docker cgroup 限制
  -XX:MaxRAMFraction=2 \             # 最大堆内存占容器内存的 1/2(对应 50%)
  # 说明:-XX:MaxRAMFraction=n 表示堆内存 = 容器内存 / n
  # 推荐值:n=2(50%)适合大多数应用,n=1(100%)可能过大
  # JDK 8 不支持 -XX:MaxRAMPercentage,需用此参数
  
  # ========== GC 日志(JDK 8 旧版语法)==========
  -Xloggc:/data/logs/gc.log \        # GC 日志输出路径
  -XX:+PrintGCDetails \              # 打印 GC 详细细节
  -XX:+PrintGCDateStamps \           # 打印日期时间戳(格式:YYYY-MM-DDTHH:MM:SS.SSS)
  -XX:+PrintGCTimeStamps \           # 打印 JVM 启动后的时间戳(毫秒)
  -XX:+PrintGCApplicationStoppedTime \  # 打印应用暂停时间
  -XX:+PrintPromotionFailure \       # 打印晋升失败信息(有助于定位 Full GC 原因)
  -XX:+PrintTenuringDistribution \   # 打印年龄分布(分析对象晋升)
  
  # ========== OOM 自动堆转储 ==========
  -XX:+HeapDumpOnOutOfMemoryError \  # 发生 OOM 时自动导出堆转储
  -XX:HeapDumpPath=/data/logs/ \     # 堆转储路径,生成文件名为 java_pid<pid>.hprof
  # 注意:JDK 8 不支持 %p 占位符,文件名固定为 java_pid<pid>.hprof
  # 如需指定完整路径:-XX:HeapDumpPath=/data/logs/heapdump.hprof(单文件覆盖)
  
  # ========== JFR 配置 ==========
  -XX:+FlightRecorder \              # 启用 Flight Recorder(JDK 8 需显式开启)
  -XX:StartFlightRecording=filename=/data/logs/recording.jfr,maxsize=500M,maxage=7d,settings=default \
  # 参数详解:
  #   filename      : 输出文件路径,支持 %p(进程 ID)占位符
  #   maxsize       : 单个文件最大大小,超过自动轮转(支持 K/M/G)
  #   maxage        : 文件保留时间(s=秒,m=分,h=时,d=天)
  #   settings      : 录制模板(default=低开销<1%,profile=详细约2%)
  #   delay         : 延迟启动时间(如 30s)
  #   duration      : 录制时长(不设置则持续录制)
  
  # ========== JMX 指标暴露 ==========
  -Dcom.sun.management.jmxremote \   # 启用 JMX 远程管理
  -Dcom.sun.management.jmxremote.port=9010 \  # JMX 服务端口
  -Dcom.sun.management.jmxremote.authenticate=false \  # 关闭认证(内网环境)
  -Dcom.sun.management.jmxremote.ssl=false \          # 关闭 SSL(内网环境)
  # 注意:不要同时设置 local.only=true,否则远程无法连接
  # 如需限制本地访问:-Dcom.sun.management.jmxremote.local.only=true
  
  # ========== 辅助诊断参数 ==========
  -XX:+PrintCommandLineFlags \       # 启动时打印显式设置的 JVM 参数
  -XX:+PrintFlagsFinal \             # 打印所有最终生效的 JVM 参数(启动后输出)
  -XX:+HeapDumpBeforeFullGC \        # Full GC 前导出堆转储(可选,谨慎使用)
  -XX:+HeapDumpAfterFullGC \         # Full GC 后导出堆转储(可选)
  -XX:ErrorFile=/data/logs/hs_err_%p.log \  # JVM 崩溃日志路径
  
  -jar your-application.jar"

4.2 JDK 11+ 完整参数模板(适用于 JDK 11/17/21)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
#!/bin/bash
JAVA_OPTS="-Xms4g -Xmx4g \

  # ========== 垃圾回收配置 ==========
  -XX:+UseG1GC \                    # 使用 G1 回收器(JDK 11+ 默认已是 G1,显式声明无妨)
  -XX:MaxGCPauseMillis=200 \        # 目标最大 GC 停顿时间
  -XX:G1HeapRegionSize=16m \        # G1 区域大小
  -XX:InitiatingHeapOccupancyPercent=45 \  # 并发标记触发阈值
  -XX:G1ReservePercent=10 \          # 预留空间百分比
  -XX:ConcGCThreads=2 \              # 并发 GC 线程数(默认根据 CPU 核数计算)
  
  # ========== 容器内存配置(JDK 10+ 支持 -XX:MaxRAMPercentage)==========
  -XX:MaxRAMPercentage=75.0 \        # 最大堆内存占容器内存的百分比(推荐 75%)
  -XX:InitialRAMPercentage=50.0 \    # 初始堆内存百分比
  -XX:MinRAMPercentage=50.0 \        # 小内存容器(<200MB)时的最小堆百分比
  # 说明:-XX:MaxRAMPercentage 比 -XX:MaxRAMFraction 更直观,推荐使用
  
  # ========== GC 日志(Unified JVM Logging)==========
  -Xlog:gc*:file=/data/logs/gc.log:time,uptime:filecount=10,filesize=100M \
  # 语法详解:-Xlog:<tag set>:<output>:<decorators>:<options>
  #   tag set       : gc* 表示所有 GC 相关标签(gc, gc+heap, gc+phases 等)
  #   output        : file= 指定文件路径
  #   decorators    : time=日期时间, uptime=JVM 启动时长, pid=进程ID, tid=线程ID
  #   options       : filecount=轮转文件数, filesize=单文件大小
  # 常用标签:
  #   gc            : 基本 GC 信息
  #   gc+heap       : 堆内存变化
  #   gc+phases     : GC 各阶段耗时
  #   gc+safepoint  : 安全点信息
  #   gc+metaspace  : 元空间信息
  
  # ========== OOM 自动堆转储 ==========
  -XX:+HeapDumpOnOutOfMemoryError \
  -XX:HeapDumpPath=/data/logs/heapdump_%p.hprof \
  # 说明:%p 占位符在 JDK 11+ 稳定支持,自动替换为进程 PID
  
  # ========== JFR 持续录制(JDK 11+ 无需解锁)==========
  -XX:StartFlightRecording=filename=/data/logs/recording.jfr,maxsize=500M,maxage=7d,settings=default \
  # 可选参数:
  #   disk=true/false     : 是否写入磁盘(默认 true)
  #   dumponexit=true/false: JVM 退出时是否转储(默认 true)
  #   maxchunksize=12M    : 单个 chunk 大小,影响轮转粒度
  
  # ========== JMX 指标暴露 ==========
  -Dcom.sun.management.jmxremote.port=9010 \
  -Dcom.sun.management.jmxremote.authenticate=false \
  -Dcom.sun.management.jmxremote.ssl=false \
  # 如需限制仅本地访问:-Dcom.sun.management.jmxremote.local.only=true
  
  # ========== 辅助诊断参数 ==========
  -XX:+PrintCommandLineFlags \
  -XX:+PrintFlagsFinal \
  -XX:ErrorFile=/data/logs/hs_err_%p.log \  # JVM 崩溃日志
  -XX:+ShowCodeDetailsInExceptionMessages \ # 异常信息中显示更多细节(JDK 14+)
  
  -jar your-application.jar"

4.3 JDK 21 特殊配置(分代 ZGC,实验性)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
# 如需启用分代 ZGC(JDK 21 实验特性,需显式开启)
-XX:+UseZGC -XX:+ZGenerational

# ZGC 专用参数
-XX:ZCollectionInterval=120 \        # 最大 GC 间隔(秒),默认 0(无限制)
-XX:ZAllocationSpikeTolerance=2.0 \  # 分配峰值容忍度,默认 2.0
-XX:ZUncommitDelay=300 \             # 未使用内存回收延迟(秒)

# 注意:
# 1. 分代 ZGC 在 JDK 21 是实验特性,生产使用需充分评估
# 2. ZGC 适合大堆内存(>16G)、极低延迟(<1ms)场景
# 3. 普通应用默认 G1 仍是稳妥选择
# 4. 虚拟线程场景下,G1 仍是官方推荐搭配

4.4 版本差异速查表

配置项JDK 8JDK 11JDK 17/21说明
默认 GCParallel GCG1G1JDK 9+ G1 为默认
GC 日志-Xloggc: + PrintGCDetails-Xlog:gc*:file=-Xlog:gc*:file=JDK 9+ 统一日志系统
容器内存-XX:+UseContainerSupport + -XX:MaxRAMFraction=n-XX:MaxRAMPercentage=n-XX:MaxRAMPercentage=nJDK 10+ 引入 Percentage 参数
JFR 开启-XX:+FlightRecorder + -XX:StartFlightRecording=直接 -XX:StartFlightRecording=直接 -XX:StartFlightRecording=JDK 11+ JFR 完全开源
堆转储文件名固定 java_pid<pid>.hprof支持 %p 占位符支持 %p 占位符JDK 11+ 文件名更灵活
JMX local.only与 port 同时设置会阻止远程连接同左同左内网采集应设为 false 或删除
异常详情基础信息基础信息-XX:+ShowCodeDetailsInExceptionMessagesJDK 14+ 增加代码位置信息

五、磁盘容量规划与维护

5.1 容量规划表

数据类型单日预估保留策略峰值占用计算依据
GC 日志200 MB10 × 100 MB1 GB4 GB 堆,G1,中等业务量
JFR 文件500 MB7 天轮转3.5 GBmaxsize=500M, maxage=7d
堆转储4 GBOOM 时生成0–8 GB建议保留最近 2 份,手动清理
JVM 崩溃日志10 MB持续100 MBhs_err 文件
应用日志按业务按业务按业务需单独规划
建议预留--10–15 GB独立分区/持久卷

5.2 自动清理脚本(crontab)

1
2
# 每日凌晨 2 点执行清理
0 2 * * * /opt/scripts/cleanup_diagnostic_logs.sh

清理脚本示例:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
#!/bin/bash
LOG_DIR="/data/logs"

# 清理 7 天前的 GC 日志
find $LOG_DIR -name "gc.log*" -mtime +7 -delete

# 清理 7 天前的 JFR 文件
find $LOG_DIR -name "*.jfr" -mtime +7 -delete

# 清理 30 天前的堆转储(OOM 可能不频繁,保留更久)
find $LOG_DIR -name "*.hprof" -mtime +30 -delete

# 清理 30 天前的崩溃日志
find $LOG_DIR -name "hs_err_*.log" -mtime +30 -delete

# 记录清理结果
echo "$(date): Cleaned up diagnostic logs older than retention period" >> /var/log/jvm_cleanup.log

六、公司内网平台:如何获取诊断数据

6.1 数据获取路径

既然无法执行 jstack/jcmd,所有数据通过公司可观测性平台获取:

数据存储路径平台获取方式注意事项
GC 日志/data/logs/gc.log*日志检索(Kibana/ELK)可全文检索、按时间过滤
JFR 文件/data/logs/recording*.jfr文件下载功能需平台支持文件浏览
堆转储/data/logs/heapdump_*.hprof文件下载功能OOM 时自动生成
JVM 指标JMX 端口 9010Prometheus 采集需配置 JMX Exporter

6.2 平台能力自检清单

使用以下问题确认公司平台是否支持完整的零命令诊断:

  • 应用启动参数是否已包含 JFR 配置?
  • 是否有 GC 日志的集中检索入口(Kibana)?
  • 是否可以从平台下载应用的 JFR 文件?
  • 是否可以从平台下载应用的堆转储文件?
  • 是否有 JVM 指标(内存、GC、线程)的监控大盘?
  • 是否支持按应用实例查看诊断数据?

6.3 故障排查流程(零命令版本)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
应用告警(CPU/内存/响应时间)
1. 登录可观测性平台
2. 查看 JVM 指标大盘
   ├── GC 次数是否异常?(YGC/FGC 频次)
   ├── GC 停顿时间是否过高?(超过 MaxGCPauseMillis)
   ├── 堆内存占用是否持续增长?
   └── 老年代占用是否接近 100%?
3. 进入 Kibana 检索 GC 日志
   ├── 查看具体 GC 事件类型(Young GC / Mixed GC / Full GC)
   ├── 分析 GC 前后堆内存变化
   └── 查看 GC 停顿时间分布
4. 如有 OOM 记录 → 下载堆转储
   ├── 用 MAT 打开
   ├── 查看 Leak Suspects 报告
   └── 分析 Dominator Tree 定位大对象
5. 如需深度分析 → 下载 JFR 文件
   ├── 用 JDK Mission Control 打开
   ├── 查看 Overview:整体 JVM 行为
   ├── 查看 Memory:GC 事件、堆变化
   ├── 查看 Threads:锁竞争、线程阻塞
   └── 查看 CPU:热点方法、调用栈
6. 输出分析报告
   ├── 问题现象描述
   ├── 根因定位(代码/配置/环境)
   ├── 影响范围
   └── 修复建议与验证方案

七、JFR 深度使用指南

7.1 JFR 是什么?

Java Flight Recorder (JFR) 是 JDK 内置的低开销性能分析框架,基于事件采集,能收集 JVM 底层运行数据。

核心优势

  • 低开销:默认配置性能损耗 < 1%,可生产环境持续开启
  • JVM 原生:直接嵌入 JVM,避免采样偏差和安全点陷阱
  • 数据完整性:应用异常退出时仍能保证数据可解析
  • 无需外部依赖:JDK 内置,无需安装额外 Agent

免费状态

  • OpenJDK 8u262+:✅ 完全免费
  • Oracle JDK 8u40+:⚠️ 需 -XX:+UnlockCommercialFeatures 解锁(旧版本)
  • OpenJDK 11+:✅ 完全免费开源

7.2 JFR 配置详解

启动时开启(推荐)

1
-XX:StartFlightRecording=filename=/data/logs/recording.jfr,maxsize=500M,maxage=7d,settings=default

配置项完整说明

参数可选值默认值说明
filename路径./recording.jfr文件输出路径,支持 %p 占位符(进程 PID)
maxsize500M无限制单文件最大大小,超过自动轮转
maxage7d无限制文件保留时间,单位 s/m/h/d
settingsdefault / profiledefault录制模板:default(低开销)、profile(详细)
delay30s0延迟启动时间
duration10m持续录制时长,不设置则持续录制
disktrue/falsetrue是否写入磁盘(false 则仅内存缓冲)
dumponexittrue/falsetrueJVM 退出时是否转储
maxchunksize12M12M单个 chunk 大小,影响轮转粒度

settings 模板对比

事件类型default 模板profile 模板
GC 事件完整采集完整采集
线程调度基础详细
锁竞争基础详细
方法采样关闭每秒采样一次
I/O 事件关闭详细
异常抛出关闭采集
预估开销< 1%约 2%

7.3 JFR 数据分析(使用 JMC)

JDK Mission Control (JMC) 是 JFR 文件的官方分析工具。

下载地址https://jdk.java.net/jmc/

分析步骤

  1. 打开 JFR 文件
    • 启动 JMC
    • File → Open File → 选择 .jfr 文件
  2. Overview 面板(全局概览)
    • JVM 信息:版本、启动参数、系统属性
    • 运行时长:应用运行总时长
    • 事件统计:各类事件数量分布
  3. Memory 面板(内存分析)
    • GC 事件列表:每次 GC 的持续时间、类型、内存变化
    • 堆内存趋势图:年轻代、老年代、元空间变化
    • 对象分配热点:哪些类分配了大量对象
    • GC 停顿分布:GC 停顿时间直方图
  4. Threads 面板(线程分析)
    • 线程状态分布:RUNNABLE、WAITING、BLOCKED 占比
    • 锁竞争分析:哪些锁被长时间持有
    • 线程阻塞事件:线程等待时长和原因
    • 死锁检测:自动识别死锁情况
  5. CPU 面板(热点分析)
    • 热点方法:按 CPU 时间排序的方法调用
    • 调用栈:方法的完整调用链
    • 线程 CPU 使用率:各线程 CPU 消耗
  6. I/O 面板(输入输出分析)
    • 文件 I/O:读写操作耗时
    • Socket I/O:网络调用耗时
    • 数据库调用:JDBC 操作耗时(需应用支持)
  7. Latency 面板(延迟分析)
    • 事件耗时分布:各类型事件的延迟情况

7.4 JFR 命令行工具

JFR 提供了 jfr 命令行工具,可对 JFR 文件进行脚本化处理:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
# 查看文件摘要信息
jfr summary recording.jfr

# 查看所有事件类型
jfr metadata recording.jfr

# 导出为文本格式(按事件类型)
jfr print recording.jfr > output.txt

# 导出为 JSON 格式(便于程序处理)
jfr print --json recording.jfr > events.json

# 按事件类型过滤
jfr print --events jdk.GarbageCollection recording.jfr

# 按类别过滤
jfr print --categories GC recording.jfr

# 按时间范围过滤
jfr print --start 10s --end 20s recording.jfr

常用事件类型

事件类型说明用途
jdk.GarbageCollectionGC 事件分析 GC 耗时
jdk.GCPhasePauseGC 阶段停顿分析 GC 内部耗时
jdk.ThreadPark线程阻塞分析线程等待
jdk.JavaMonitorWait锁等待分析锁竞争
jdk.ObjectAllocationOutsideTLAB大对象分配排查大对象问题
jdk.ExceptionThrown异常抛出排查异常频繁问题
jdk.CPULoadCPU 负载分析 CPU 使用率

7.5 高频问题:JFR 无法启用怎么办?

现象可能原因解决方案
启动参数无效JDK 版本过低升级到 JDK 8u262+ 或 JDK 11+
JFR 文件未生成目录不存在或不可写创建目录并授权:mkdir -p /data/logs && chmod 755 /data/logs
JMC 打不开文件JFR 文件损坏检查文件完整性,确认录制正常结束;尝试 jfr summary 验证
性能开销过高使用了 profile 模板或 CPU 采样改用 default 模板;调整采样频率
提示 FlightRecorder not supportedOracle JDK 8 未解锁添加 -XX:+UnlockCommercialFeatures(仅限旧版 Oracle JDK)
JFR 文件过大maxsize 设置过大或录制时间过长设置合理的 maxsize 和 maxage

八、个人项目:常用诊断命令速查

以下命令适用于可以自由执行诊断的场景,供日常学习和排查使用。

8.1 进程与基本信息

命令说明示例输出解读
jps -l列出所有 Java 进程jps -l输出 PID 和主类名/JAR 路径
jinfo <pid>查看 JVM 参数和系统属性jinfo 12345显示 VM Flags 和 System Properties
jstat -gcutil <pid> 1s实时监控 GC 情况jstat -gcutil 12345 1s每秒打印 GC 百分比和次数

jstat 输出解读

1
2
S0     S1     E      O      M     CCS    YGC     YGCT    FGC    FGCT     GCT
0.00  98.23  45.67  72.34  88.12  82.45   125    2.345     2    0.123   2.468
  • S0/S1:Survivor 区使用率
  • E:Eden 区使用率
  • O:Old 区使用率(持续接近 100% 表示内存泄漏或堆过小)
  • M:Metaspace 使用率
  • YGC:Young GC 次数
  • YGCT:Young GC 总耗时(秒)
  • FGC:Full GC 次数(应接近 0,频繁表示严重问题)
  • FGCT:Full GC 总耗时

8.2 线程诊断

命令说明使用场景
jstack <pid>导出线程快照CPU 100%、死锁、接口卡死
jstack <pid> > thread.log导出到文件便于分析
top -Hp <pid>查看线程 CPU 占用找到 CPU 最高的线程 ID

CPU 100% 排查流程

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# 1. 找到 Java 进程 PID
jps -l

# 2. 找到 CPU 最高的线程 ID(十进制)
top -Hp 12345 -n 1

# 3. 将线程 ID 转换为十六进制
printf "%x\n" 12346

# 4. 导出线程快照,搜索该十六进制 ID
jstack 12345 | grep -A 20 "0x303a"

线程快照关键状态

状态含义常见原因
RUNNABLE正在执行CPU 计算、系统调用、网络等待
BLOCKED等待锁锁竞争、死锁
WAITING无限期等待Object.wait()LockSupport.park()
TIMED_WAITING有限期等待Thread.sleep()、带超时的 wait()

8.3 内存诊断

命令说明使用场景
jmap -heap <pid>查看堆内存分布检查各代内存使用情况
`jmap -histo:live head -20`查看存活对象统计
jmap -dump:live,format=b,file=heap.hprof <pid>导出堆转储内存泄漏分析

⚠️** 注意事项**:

  • jmap -dump 会触发 Stop-The-World,建议在业务低峰期执行
  • 堆转储文件大小约等于堆内存大小,需预留足够磁盘空间
  • -histo:live 也会触发 Full GC,谨慎使用

jmap -heap 输出解读

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
Heap Configuration:
   MinHeapFreeRatio         = 40
   MaxHeapFreeRatio         = 70
   MaxHeapSize              = 4294967296 (4096.0MB)  # 最大堆
   NewSize                  = 142606336 (136.0MB)    # 年轻代初始大小
   MaxNewSize               = 1431830528 (1365.5MB)  # 年轻代最大大小
   OldSize                  = 286261248 (273.0MB)    # 老年代大小

Heap Usage:
G1 Heap:
   regions = 4096
   capacity = 4294967296 (4096.0MB)
   used     = 3221225472 (3072.0MB)    # 已使用堆
   free     = 1073741824 (1024.0MB)    # 剩余堆

8.4 JFR 动态管理(JDK 11+)

命令说明
jcmd <pid> JFR.start name=MyRecording filename=recording.jfr动态开启录制
jcmd <pid> JFR.check查看当前录制状态
jcmd <pid> JFR.stop name=MyRecording停止录制
jcmd <pid> JFR.dump name=MyRecording filename=output.jfr导出录制文件

8.5 Arthas 快速上手

Arthas 是阿里开源的 Java 诊断工具,无需 JVM 参数即可运行。

启动

1
./as.sh <pid>

常用命令

命令说明示例
dashboard实时监控面板dashboard
thread查看线程栈thread -n 5(查看最繁忙的 5 个线程)
thread -b查看死锁thread -b
jad反编译类jad com.example.Service
watch监控方法入参出参watch com.example.Service methodName returnObj
trace追踪方法调用路径trace com.example.Service methodName
jfr动态开启 JFRjfr start / jfr stop
sc搜索类sc -d com.example.*
sm搜索方法sm com.example.Service

退出exitquit


九、高频问题与排查案例

9.1 OOM 发生后如何分析?

现象:应用日志出现 java.lang.OutOfMemoryError: Java heap space

排查步骤

  1. 确认堆转储文件已生成
1
ls -lh /data/logs/*.hprof
  1. 下载文件到本地(通过平台或文件传输)
  2. 使用 MAT 打开
  3. 查看 Leak Suspects 报告
    • MAT 自动分析可能的泄漏点
    • 报告显示大对象、GC Roots 路径
  4. 分析 Dominator Tree
    • 按 “Retained Heap” 排序
    • 找出占用内存最大的对象
    • 追踪到 GC Roots 的引用链
  5. 查看对象类型分布
    • Histogram 视图显示各类型对象数量
    • 关注数量异常或占用内存大的类型

常见原因

  • 内存泄漏:集合类(HashMapArrayList)无限制增长
  • 大对象:单个对象过大(如大 List、大 String)
  • 外部资源未释放:数据库连接、文件流未关闭
  • 缓存不当:缓存无过期策略或容量限制

9.2 CPU 飙高 100% 如何快速定位?

现象top 显示 Java 进程 CPU 使用率接近 100%

排查步骤(可执行命令场景)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# 1. 找到 Java 进程 PID
jps -l

# 2. 查看进程中线程 CPU 占用
top -Hp 12345 -n 1

# 3. 记录 CPU 最高的线程 ID(十进制),转为十六进制
printf "%x\n" 12346

# 4. 导出线程快照,查找该线程的堆栈
jstack 12345 | grep -A 30 "0x303a"

常见原因

  • 死循环:while 循环缺少退出条件
  • 频繁 GC:可通过 jstat 确认 GC 是否频繁
  • 正则表达式回溯:CPU 集中在 Pattern 相关方法
  • 大量对象序列化/反序列化:JSON 解析、对象转换
  • 加密/解密操作:高频加解密操作

通过 JFR 分析(更精确)

  1. 开启 JFR:jcmd 12345 JFR.start name=CPUSample
  2. 采集 30 秒
  3. 停止并导出:jcmd 12345 JFR.stop name=CPUSample filename=cpu.jfr
  4. 用 JMC 打开,查看 CPU 面板的热点方法

9.3 应用频繁 Full GC 怎么办?

现象jstat 显示 FGC 持续增加,GC 日志显示频繁 Full GC

排查步骤

  1. 查看 GC 日志,确认触发原因
    • JDK 8:grep "Full GC" gc.log -A 5
    • JDK 11+:grep "Pause Full" gc.log -A 5
  2. 区分触发类型
    • Old 区满:内存泄漏或堆太小
    • Metaspace 满:类加载过多或 Metaspace 太小
    • System.gc():代码中显式调用
  3. 导出堆转储分析
1
jmap -dump:live,format=b,file=heap.hprof 12345
  1. **检查是否代码中调用了 **System.gc()
    • 可通过 -XX:+DisableExplicitGC 禁用(谨慎使用)

常见原因

  • 内存泄漏:对象无法回收,Old 区持续增长
  • 大对象直接进入老年代:检查 -XX:PretenureSizeThreshold
  • Metaspace 不足-XX:MaxMetaspaceSize 设置过小
  • 显式 GCSystem.gc() 或框架调用

9.4 接口响应变慢如何定位?

现象:接口耗时从 50ms 增长到 500ms

排查步骤

  1. 下载 JFR 文件(如已开启持续录制)
  2. 用 JMC 打开,查看 Method Profiling 面板
    • 找到 CPU 占用最高的方法
    • 查看调用栈,定位热点代码
  3. 查看 Threads 面板
    • 检查是否存在锁竞争(BLOCKED 状态线程)
    • 查看锁等待时长
  4. 查看 I/O 面板
    • 数据库调用耗时
    • 外部 HTTP 调用耗时
    • 文件读写耗时
  5. 查看 GC 面板
    • GC 停顿是否频繁
    • GC 停顿时间是否过长

常见原因

  • 数据库慢查询:SQL 未命中索引、数据量大
  • 锁竞争synchronized 块、ReentrantLock 等待
  • GC 停顿:频繁 Full GC 或 GC 停顿过长
  • 外部接口超时:下游服务响应慢
  • 线程池满:任务排队等待

9.5 死锁如何检测和定位?

现象:应用卡死,部分线程不响应

排查步骤

  1. 导出线程快照
1
jstack 12345 > thread.log
  1. 搜索死锁信息
1
grep "deadlock" thread.log -A 20
  1. JVM 会自动检测死锁,输出类似:
1
2
3
4
5
6
7
8
Found one Java-level deadlock:
=============================
"Thread-1":
  waiting to lock monitor 0x00007f8c2c005e00 (object 0x00000007e2a8b5c0, a java.lang.Object),
  which is held by "Thread-0"
"Thread-0":
  waiting to lock monitor 0x00007f8c2c006c00 (object 0x00000007e2a8b5d0, a java.lang.Object),
  which is held by "Thread-1"
  1. 分析代码,检查锁的获取顺序

预防措施

  • 统一锁的获取顺序
  • 使用 tryLock() 设置超时
  • 减少锁的持有时间
  • 使用更细粒度的锁

十、总结与行动清单

10.1 核心要点回顾

  1. 零命令依赖:通过 JVM 参数自动导出 GC 日志、堆转储、JFR 文件
  2. 版本差异:JDK 8/11/21 在 GC 日志、容器内存、JFR 参数上有明显差异,需分版本配置
  3. 磁盘规划:预留 10–15 GB 独立分区用于诊断数据
  4. JFR 是核心:持续开启,生产环境开销 < 1%,事后可还原完整现场
  5. 平台能力:充分利用公司可观测性平台获取诊断数据
  6. 事后分析:使用 JMC、MAT、GCViewer 等工具深度分析
  7. 个人项目:掌握 jstackjmapjstat、Arthas 等诊断命令

10.2 版本选择速查

场景推荐版本关键配置
JDK 8 项目OpenJDK 8u262+-XX:+UseG1GC -Xloggc:gc.log -XX:+FlightRecorder
JDK 11 项目OpenJDK 11-XX:MaxRAMPercentage=75 -Xlog:gc*:file=gc.log
JDK 17 项目OpenJDK 17同 JDK 11,稳定可靠
JDK 21 项目OpenJDK 21同 JDK 11,虚拟线程可选用

10.3 行动清单

立即执行

  • 检查应用 JDK 版本,确认 ≥ 8u262 或 11+
  • 检查应用启动参数,确认包含 JFR 和 GC 日志配置
  • 确认 /data/logs 目录存在且有写入权限
  • 评估磁盘容量,确保足够存储诊断数据(建议 10-15 GB)
  • 在可观测性平台验证能否检索 GC 日志

短期规划

  • 建立标准化启动参数模板,按 JDK 版本分别维护
  • 梳理平台支持的功能,明确数据获取方式
  • 搭建本地 JMC 环境,练习 JFR 分析
  • 配置自动清理脚本(crontab),防止磁盘写满

长期建设

  • 建立故障排查 SOP 文档,形成团队知识库
  • 组织团队 JFR 分析培训
  • 推动平台集成 JFR 在线分析能力
  • 建立诊断数据的定期审查机制

10.4 常见问题速查表

问题现象优先查看定位手段典型原因
OOM堆转储MAT 分析内存泄漏、大对象
CPU 100%线程快照jstack + top -Hp死循环、频繁 GC、正则回溯
频繁 Full GCGC 日志GCViewer / JMC内存泄漏、堆太小、System.gc()
接口变慢JFRJMC Method Profiling锁竞争、慢 SQL、外部超时
应用卡死线程快照jstack 搜索死锁死锁、线程阻塞

10.5 延伸阅读

END