DukeDuke
主页
项目文档
技术文档
  • 单机版
  • 微服务
  • 代办项目
  • 优鲜项目
项目管理
关于我们
主页
项目文档
技术文档
  • 单机版
  • 微服务
  • 代办项目
  • 优鲜项目
项目管理
关于我们
  • 技术文档

    • 网络原理

      • 交换机
      • 路由器
      • TCP/IP协议
      • HTTP 与 HTTPS
    • 软件架构

      • 什么是软件架构
      • 分层架构
      • 微服务架构
      • 事件驱动架构
      • 领域驱动设计(DDD)
      • 架构图
      • 高并发系统
    • Vue3

      • Vue3简介
      • Vue3响应式系统
      • Vue3组合式API
      • Vue3生命周期
      • Vue3模板语法
      • Vue3组件系统
      • Vue3 路由系统
      • Vue3 状态管理
      • Vue3 性能优化
      • Vue3 TypeScript 支持
      • Vue3 项目实战
      • VUE 面试题大全
      • Node.js 安装
    • JAVA

      • JVM

        • 认识JVM
        • JVM类加载器
        • 运行时数据区
        • 执行引擎
        • 本地方法接口
        • 本地方法库
        • JVM垃圾回收
        • JVM性能监控
        • JVM调优
      • 设计模式
        • 单例模式
        • 工厂模式
        • 策略模式
        • 适配器模式
        • 建造者模式
        • 原型模式
        • 装饰器模式
        • 代理模式
        • 外观模式
        • 享元模式
        • 组合模式
        • 桥接模式
      • Java多线程

        • Java 线程基础详解
        • Java 线程池详解
        • Java ThreadLocal 详解
        • Java volatile 详解
        • Java 线程间通信详解
        • Java 线程安全详解
        • Java 线程调度详解
        • Java 线程优先级详解

        • Java 线程中断详解
        • Java 线程死锁详解
      • Java反射
      • Java 面试题

        • Java 基础概念面试题
        • Java 面向对象编程面试题
        • Java 集合框架面试题
        • Java 多线程与并发面试题
        • JVM 与内存管理面试题
        • Java I/O 与 NIO 面试题
        • Java 异常处理面试题
        • Java 反射与注解面试题
        • Java Spring 框架面试题
        • Java 数据库与 JDBC 面试题
        • Java 性能优化面试题
        • Java 实际项目经验面试题
        • Java 高级特性面试题
        • Java 面试准备建议
    • Python

      • Python简介
      • Python安装
      • Python hello world
      • Python基础语法
      • Python数据类型
      • Python数字
      • Python字符串
      • Python列表
      • Python元组
      • Python字典
      • Python日期时间
      • Python文件操作
      • Python异常处理
      • Python函数
      • Python类
      • Python模块
      • Python包
      • Python多线程
      • Python面向对象
      • Python爬虫
      • Django web框架
      • Python 面试题

        • Python 面试题导航
        • Python 基础概念
        • Python 面向对象编程
        • Python 数据结构
        • Python 高级特性
        • Python 框架
        • Python 性能优化
        • Python 项目经验
    • Spring

      • Spring
      • Springboot
      • Spring Security 安全框架
      • SpringBoot 中的事件详解
      • SpringBoot 中的定时任务详解
      • SpringBoot 自动装配原理与源码解释
    • Mybatis

      • Mybatis
      • Mybatis-Plus
    • 数据库

      • Redis

        • Redis简介
        • Redis(单机)安装
        • Redis配置
        • Redis数据结构
        • RDB、AOF 和混合持久化机制
        • Redis内存管理
        • Redis缓存一致性
        • Redis缓存穿透
        • Redis缓存击穿
        • Redis缓存雪崩
        • Redis Lua脚本
        • Redis主从复制
        • Redis哨兵模式
        • Redis集群
        • Redis数据分片
        • Redis CPU使用率过高
        • Redis面试题
      • MySQL

        • MySQL简介
        • MySQL安装
        • MySQL配置
        • MYSQL日常维护
        • MYSQL优化-慢查询
        • MYSQL优化-索引
        • MYSQL数据库设计规范
    • 消息队列

      • RocketMQ
      • Kafka
      • RabbitMQ
      • 消息队列面试题
    • 微服务

      • SpringCloud 微服务
      • Eureka 注册中心
      • Nacos 注册中心
      • Gateway 网关
      • Feign 服务调用
      • Sentinel 限流 与 熔断
      • Seata 分布式事务
      • CAP 理论
      • Redis 分布式锁
      • 高并发系统设计
    • ELK日志分析系统

      • Elasticsearch 搜索引擎
      • Logstash 数据处理
      • Kibana 可视化
      • ELK 实战
    • 开放API

      • 开放API设计
      • 开放API示例项目
    • 人工智能

      • 人工智能简介
      • 机器学习

      • 深度学习

      • 自然语言处理

      • 计算机视觉

        • CUDA与cuDNN详细安装
        • Conda 安装
        • Pytorch 深度学习框架
        • yolo 目标检测
        • TensorRT 深度学习推理优化引擎
        • TensorFlow 机器学习
        • CVAT 图像标注
        • Windows 下安装 CUDA、cuDNN、TensorRT、TensorRT-YOLO 环境
        • Windows10+CUDA+cuDNN+TensorRT+TensorRT-YOLO 部署高性能YOLO11推理
    • 大数据

      • 大数据简介
      • Hadoop 数据存储
      • Flume 数据采集
      • Sqoop 数据导入导出
      • Hive 数据仓库
      • Spark 数据处理
      • Flink 数据处理
      • Kafka 数据采集
      • HBase 数据存储
      • Elasticsearch 搜索引擎
    • 图像处理

      • 图像处理简介
      • 医学图像web呈现
      • 医学图像处理
      • 切片细胞分离问题
    • 服务器&运维

      • Linux 系统

        • Linux 系统管理
        • Linux 网络管理
        • Linux 文件管理
        • Linux 命令大全
      • Nginx Web 服务器

        • Nginx 安装 与 配置
        • Nginx 负载均衡
        • Nginx SSL证书配置
        • Nginx Keepalived 高可用
      • Docker 容器

        • Docker 简介
        • Docker 安装与配置
        • Docker 命令
        • Docker 部署 Nginx
        • Docker 部署 MySQL
        • Docker 部署 Redis
      • 服务器

        • 塔式服务器
        • 机架式服务器
        • 刀片服务器
      • Git 版本控制
      • Jenkins 持续集成
      • Jmeter 性能测试
      • Let's Encrypt 免费SSL证书
    • 简历

      • 项目经理简历
      • 开发工程师简历

JVM 性能监控

什么是 JVM 性能监控?

简单来说,JVM 性能监控就像是给 Java 程序做体检。就像医生通过检查血压、心跳、体温来判断人的健康状况一样,我们通过监控 JVM 的各种指标来判断 Java 程序是否健康运行。

为什么要做 JVM 性能监控?

想象一下,你的 Java 应用就像一辆汽车:

  • 如果发动机(CPU)过热,车子会抛锚
  • 如果油箱(内存)没油了,车子会熄火
  • 如果轮胎(线程)磨损严重,车子会失控

JVM 性能监控就是帮我们提前发现这些问题,避免程序崩溃。

常用监控工具

jcmd

jcmd 是 JDK 7 引入的一个多功能诊断命令工具,它整合了 jps、jstack、jmap、jinfo 等工具的功能,是 JVM 诊断的瑞士军刀。jcmd 可以执行各种诊断命令,包括线程转储、堆转储、JFR 记录等。

基本语法

jcmd [options] <pid> <command> [arguments]

参数说明:

  • options:选项参数
  • pid:Java 进程 ID(通过 jps 获取)
  • command:要执行的命令
  • arguments:命令参数

常用选项

# 列出所有可用的命令
jcmd 12345 help

# 列出所有 Java 进程
jcmd -l

# 连接到远程 JVM
jcmd host:port <command>


# 查看正在运行的JVM参数
jcmd pid VM.flags

#输出示例:
-XX:CICompilerCount=2 -XX:InitialHeapSize=268435456 -XX:MaxHeapSize=536870912 -XX:MaxNewSize=178913280 -XX:MinHeapDeltaBytes=196608 -XX:MinHeapSize=268435456 -XX:NewSize=89456640 -XX:NonNMethodCodeHeapSize=5826188 -XX:NonProfiledCodeHeapSize=122916026 -XX:OldSize=178978816 -XX:ProfiledCodeHeapSize=122916026 -XX:ReservedCodeCacheSize=251658240 -XX:+SegmentedCodeCache -XX:SoftMaxHeapSize=536870912 -XX:-THPStackMitigation -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseSerialGC

JVM 参数详细解释

内存相关参数:

  • -XX:InitialHeapSize=268435456:初始堆大小 = 256MB (268435456 字节)
  • -XX:MaxHeapSize=536870912:最大堆大小 = 512MB (536870912 字节)
  • -XX:MinHeapSize=268435456:最小堆大小 = 256MB
  • -XX:SoftMaxHeapSize=536870912:软最大堆大小 = 512MB

年轻代配置:

  • -XX:NewSize=89456640:年轻代初始大小 = 85.3MB
  • -XX:MaxNewSize=178913280:年轻代最大大小 = 170.6MB

老年代配置:

  • -XX:OldSize=178978816:老年代大小 = 170.7MB

垃圾收集器配置:

  • -XX:+UseSerialGC:使用串行垃圾收集器(单线程 GC)
  • -XX:MinHeapDeltaBytes=196608:堆大小调整的最小增量 = 192KB

代码缓存配置:

  • -XX:ReservedCodeCacheSize=251658240:代码缓存总大小 = 240MB
  • -XX:+SegmentedCodeCache:启用分段代码缓存
  • -XX:NonNMethodCodeHeapSize=5826188:非方法代码堆大小 = 5.6MB
  • -XX:NonProfiledCodeHeapSize=122916026:非分析代码堆大小 = 117.2MB
  • -XX:ProfiledCodeHeapSize=122916026:分析代码堆大小 = 117.2MB

编译器配置:

  • -XX:CICompilerCount=2:C1 编译器线程数 = 2 个

内存优化配置:

  • -XX:+UseCompressedOops:启用压缩对象指针(节省内存)
  • -XX:+UseCompressedClassPointers:启用压缩类指针(节省内存)

系统配置:

  • -XX:-THPStackMitigation:禁用透明大页栈缓解(Linux 系统)

配置分析

优点:

  1. 内存配置合理:堆内存从 256MB 到 512MB,适合中小型应用
  2. 启用内存压缩:使用压缩指针节省内存空间
  3. 代码缓存充足:240MB 代码缓存足够 JIT 编译使用

潜在问题:

  1. 使用串行 GC:UseSerialGC适合单核或内存很小的场景,多核环境下性能较差
  2. 堆内存较小:512MB 最大堆可能不够大型应用使用
  3. 编译器线程少:只有 2 个 C1 编译器线程,可能影响编译效率

建议优化:

  • 对于多核环境,考虑使用 -XX:+UseG1GC 或 -XX:+UseParallelGC
  • 根据应用需求适当增加堆内存大小
  • 考虑增加编译器线程数 -XX:CICompilerCount=4

这个配置看起来是一个相对保守的 JVM 配置,适合资源受限的环境或小型应用。

jps(Java Process Status)

# 查看所有Java进程(显示PID和主类名)
jps

# 输出示例:
# 12345 MyApplication
# 67890 SpringBootApp
# 11111 org.apache.tomcat.Main
# 查看所有 Java 进程(显示传递给 main 方法的参数)
jps -m

# 输出示例:
# 12345 MyApplication --server.port=8080
# 67890 SpringBootApp --spring.profiles.active=prod
# 查看所有Java进程(显示传递给JVM的参数)
jps -v

# 输出示例:
# 12345 MyApplication -Xms512m -Xmx1024m
# 67890 SpringBootApp -Dspring.profiles.active=prod

jstat(Java Statistics Monitoring Tool)

jstat 是 JVM 自带的统计监控工具,用于监控 JVM 的各种统计信息,包括内存使用、GC 情况、类加载等。

基本语法

jstat [options] <vmid> [interval] [count]

参数说明:

  • options:监控选项
  • vmid:Java 进程 ID(通过 jps 获取)
  • interval:监控间隔(毫秒)
  • count:监控次数

常用监控选项

1. 监控 GC 情况

# 监控GC统计信息
jstat -gc 12345 1000 10

# 输出示例:
#  S0C    S1C    S0U    S1U      EC       EU        OC         OU       MC     MU    CCSC   CCSU   YGC     YGCT    FGC    FGCT     GCT
# 512.0  512.0   0.0    0.0    2048.0   1024.0    4096.0     2048.0   4480.0 4096.0  512.0  384.0      5    0.050     2    0.100    0.150

输出解释:

  • S0C/S1C:Survivor 0/1 区容量(KB)
  • S0U/S1U:Survivor 0/1 区使用量(KB)
  • EC:Eden 区容量(KB)
  • EU:Eden 区使用量(KB)
  • OC:老年代容量(KB)
  • OU:老年代使用量(KB)
  • MC:元空间容量(KB)
  • MU:元空间使用量(KB)
  • CCSC/CCSU:压缩类空间容量/使用量(KB)
  • YGC:年轻代 GC 次数
  • YGCT:年轻代 GC 总耗时(秒)
  • FGC:Full GC 次数
  • FGCT:Full GC 总耗时(秒)
  • GCT:GC 总耗时(秒)

异常参数:

  • FGC > 10:Full GC 次数过多,可能存在内存泄漏
  • FGCT > 5秒:Full GC 耗时过长,影响应用响应
  • OU/OC > 0.9:老年代使用率过高,接近 OOM
  • EU/EC > 0.95:Eden 区使用率过高,GC 频繁

解决方案:

  • 内存泄漏:使用 jmap 导出堆转储,用 MAT 分析内存泄漏
  • Full GC 耗时过长:调整 GC 参数,如 -XX:MaxGCPauseMillis=200
  • 老年代使用率过高:增加堆内存 -Xmx,或优化代码减少对象创建
  • Eden 区使用率过高:增加年轻代大小 -Xmn,或调整 Survivor 比例

2. 监控 GC 使用率

# 监控GC使用率
jstat -gcutil 12345 1000 10

# 输出示例:
#  S0     S1     E      O      M     CCS    YGC     YGCT    FGC    FGCT     GCT
#  0.00   0.00  50.00  50.00  91.40  75.00      5    0.050     2    0.100    0.150

输出解释:

  • S0/S1:Survivor 0/1 区使用率(%)
  • E:Eden 区使用率(%)
  • O:老年代使用率(%)
  • M:元空间使用率(%)
  • CCS:压缩类空间使用率(%)
  • YGC/YGCT:年轻代 GC 次数/耗时
  • FGC/FGCT:Full GC 次数/耗时
  • GCT:GC 总耗时

异常参数:

  • O > 85%:老年代使用率过高,需要关注
  • O > 95%:老年代使用率极高,OOM 风险
  • M > 90%:元空间使用率过高,可能存在类加载问题
  • FGC > 1次/分钟:Full GC 频率过高
  • GCT > 10%:GC 总耗时占比过高,影响性能

解决方案:

  • 老年代使用率过高:增加堆内存,使用 G1GC,优化对象生命周期
  • OOM 风险:立即增加 -Xmx,检查内存泄漏,考虑重启应用
  • 元空间使用率过高:增加 -XX:MaxMetaspaceSize,检查动态类生成
  • Full GC 频率过高:调整 GC 算法,优化内存分配
  • GC 耗时占比过高:使用低延迟 GC(如 ZGC),优化 GC 参数

3. 监控内存使用

# 监控内存使用情况
jstat -memory 12345 1000 10

# 输出示例:
#  NGCMN    NGCMX     NGC     S0C   S1C       EC      OGCMN      OGCMX       OGC         OC       MCMN     MCMX      MC     CCSMN    CCSMX     CCSC    YGC    FGC
# 512.0   1024.0    512.0   64.0   64.0    384.0     1024.0    4096.0     4096.0     4096.0      0.0  4480.0   4480.0      0.0  512.0    512.0      5     2

输出解释:

  • NGCMN/NGCMX/NGC:年轻代最小/最大/当前容量(KB)
  • S0C/S1C:Survivor 0/1 区容量(KB)
  • EC:Eden 区容量(KB)
  • OGCMN/OGCMX/OGC:老年代最小/最大/当前容量(KB)
  • OC:老年代容量(KB)
  • MCMN/MCMX/MC:元空间最小/最大/当前容量(KB)
  • CCSMN/CCSMX/CCSC:压缩类空间最小/最大/当前容量(KB)

异常参数:

  • NGC < NGCMN:年轻代容量异常,配置问题
  • OGC < OGCMN:老年代容量异常,配置问题
  • MC > MCMX * 0.9:元空间接近最大值
  • CCSC > CCSMX * 0.9:压缩类空间接近最大值

解决方案:

  • 年轻代容量异常:检查 -Xmn 参数配置,确保合理设置
  • 老年代容量异常:检查 -Xms 和 -Xmx 参数,确保最小堆设置合理
  • 元空间接近最大值:增加 -XX:MaxMetaspaceSize,检查类加载器泄漏
  • 压缩类空间接近最大值:增加 -XX:CompressedClassSpaceSize,优化类结构

4. 监控类加载

# 监控类加载情况
jstat -class 12345 1000 10

# 输出示例:
# Loaded  Bytes  Unloaded  Bytes     Time
#   1234  2048.0       56   128.0       0.05

输出解释:

  • Loaded:已加载的类数量
  • Bytes:已加载类的字节数(KB)
  • Unloaded:已卸载的类数量
  • Bytes:已卸载类的字节数(KB)
  • Time:类加载耗时(秒)

异常参数:

  • Loaded > 10000:类加载数量过多,可能存在类加载器泄漏
  • Unloaded > Loaded * 0.5:类卸载比例过高,可能存在类加载问题
  • Time > 1秒:类加载耗时过长,影响启动性能
  • Bytes > 100MB:类字节数过多,可能存在大量动态类生成

解决方案:

  • 类加载数量过多:检查类加载器泄漏,使用 jmap 分析类加载器
  • 类卸载比例过高:检查动态代理、反射等动态类生成,优化类加载策略
  • 类加载耗时过长:使用类预加载,优化类路径,减少不必要的类加载
  • 类字节数过多:检查动态类生成(如 CGLIB、Javassist),优化代码生成策略

5. 监控编译器

# 监控JIT编译器情况
jstat -compiler 12345 1000 10

# 输出示例:
# Compiled Failed Invalid   Time   FailedType FailedMethod
#      1234     0       0    0.05          0

输出解释:

  • Compiled:编译任务数
  • Failed:编译失败数
  • Invalid:无效编译数
  • Time:编译耗时(秒)
  • FailedType:失败类型
  • FailedMethod:失败方法

异常参数:

  • Failed > 0:存在编译失败,需要关注
  • Failed > Compiled * 0.1:编译失败率过高
  • Invalid > 0:存在无效编译,可能存在代码问题
  • Time > 10秒:编译耗时过长,影响性能
  • FailedMethod > 0:存在方法编译失败,需要检查代码

解决方案:

  • 编译失败:检查 JVM 版本兼容性,查看编译日志,更新 JVM
  • 编译失败率过高:检查代码复杂度,优化热点方法,减少动态代码生成
  • 无效编译:检查代码中的异常处理,优化 try-catch 块
  • 编译耗时过长:调整编译阈值 -XX:CompileThreshold,使用分层编译
  • 方法编译失败:检查方法大小限制,优化复杂方法,拆分大方法

jstack(Java Stack Trace)

jstack 是 JVM 自带的线程堆栈分析工具,用于生成 Java 进程中所有线程的堆栈跟踪信息,帮助诊断线程死锁、线程阻塞、CPU 使用率高等问题。

基本语法

jstack [options] <pid>

参数说明:

  • options:选项参数
  • pid:Java 进程 ID(通过 jps 获取)

常用选项

# 基本用法:生成线程堆栈
jstack 12345

# 强制生成堆栈(即使进程挂起)
jstack -F 12345

# 生成长格式的堆栈信息
jstack -l 12345

# 生成长格式并强制生成
jstack -F -l 12345

输出示例及分析

1. 正常线程堆栈

jstack 12345

# 输出示例:
# 2024-01-15 10:30:45
# Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.291-b10 mixed mode):
#
# "main" #1 prio=5 os_prio=0 tid=0x00007f8c4c009000 nid=0x1234 runnable [0x00007f8c4d123000]
#    java.lang.Thread.State: RUNNABLE
#         at java.net.SocketInputStream.socketRead0(Native Method)
#         at java.net.SocketInputStream.socketRead(SocketInputStream.java:116)
#         at java.net.SocketInputStream.read(SocketInputStream.java:171)
#         at java.net.SocketInputStream.read(SocketInputStream.java:141)
#         at java.io.BufferedInputStream.fill(BufferedInputStream.java:246)
#         at java.io.BufferedInputStream.read(BufferedInputStream.java:265)
#         at java.io.ObjectInputStream$PeekInputStream.read(ObjectInputStream.java:2688)
#         at java.io.ObjectInputStream$PeekInputStream.peek(ObjectInputStream.java:2702)
#         at java.io.ObjectInputStream$BlockDataInputStream.peek(ObjectInputStream.java:2716)
#         at java.io.ObjectInputStream$BlockDataInputStream.peekByte(ObjectInputStream.java:2726)
#         at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1381)
#         at java.io.ObjectInputStream.readObject(ObjectInputStream.java:373)
#         at com.example.MyApplication.main(MyApplication.java:25)
#
# "GC task thread#0 (ParallelGC)" os_prio=0 tid=0x00007f8c4c02a800 nid=0x1235 runnable
#
# "GC task thread#1 (ParallelGC)" os_prio=0 tid=0x00007f8c4c02c800 nid=0x1236 runnable
#
# "VM Thread" os_prio=0 tid=0x00007f8c4c025800 nid=0x1237 runnable
#
# "VM Periodic Task Thread" os_prio=0 tid=0x00007f8c4c02e800 nid=0x1238 waiting on condition
#
# JNI global references: 1234

输出解释:

  • "main":线程名称
  • #1:线程编号
  • prio=5:线程优先级
  • os_prio=0:操作系统优先级
  • tid=0x00007f8c4c009000:线程 ID
  • nid=0x1234:本地线程 ID(对应操作系统线程 ID)
  • runnable:线程状态
  • [0x00007f8c4d123000]:线程栈地址

线程状态说明:

  • RUNNABLE:可运行状态,正在执行或等待 CPU
  • BLOCKED:阻塞状态,等待获取锁
  • WAITING:等待状态,等待其他线程通知
  • TIMED_WAITING:限时等待状态
  • TERMINATED:终止状态

2. 死锁检测

jstack -l 12345

# 输出示例(发现死锁):
# Found one Java-level deadlock:
# =============================
# "Thread-1":
#   waiting to lock monitor 0x00007f8c4c123456 (object 0x00000000d1234567, a java.lang.Object),
#   which is held by "Thread-2"
# "Thread-2":
#   waiting to lock monitor 0x00007f8c4c123789 (object 0x00000000d1234568, a java.lang.Object),
#   which is held by "Thread-1"
#
# Java stack information for the threads listed above:
# ===================================================
# "Thread-1":
#         at com.example.DeadlockExample.methodA(DeadlockExample.java:25)
#         - waiting to lock <0x00000000d1234567> (a java.lang.Object)
#         at com.example.DeadlockExample.run(DeadlockExample.java:15)
# "Thread-2":
#         at com.example.DeadlockExample.methodB(DeadlockExample.java:35)
#         - waiting to lock <0x00000000d1234568> (a java.lang.Object)
#         at com.example.DeadlockExample.run(DeadlockExample.java:20)

死锁分析:

  • 死锁特征:两个或多个线程互相等待对方持有的锁
  • 检测方法:使用 jstack -l 查看锁信息
  • 常见场景:数据库连接池、资源竞争、不当的锁顺序

3. 线程阻塞分析

jstack 12345

# 输出示例(线程阻塞):
# "http-nio-8080-exec-1" #23 daemon prio=5 os_prio=0 tid=0x00007f8c4c123456 nid=0x1239 waiting for monitor entry [0x00007f8c4d456000]
#    java.lang.Thread.State: BLOCKED (on object monitor)
#         at com.example.UserService.getUser(UserService.java:45)
#         - waiting to lock <0x00000000d1234567> (a java.util.concurrent.ConcurrentHashMap)
#         at com.example.UserController.getUser(UserController.java:25)
#         at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:118)
#         at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:895)
#         at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:808)
#         at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handle(RequestMappingHandlerAdapter.java:134)
#         at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:967)
#         at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:925)
#         at org.springframework.web.servlet.DispatcherServlet.service(DispatcherServlet.java:889)
#         at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:851)
#         at javax.servlet.http.HttpServlet.service(HttpServlet.java:729)
#         at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231)
#         at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
#         at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:202)
#         at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96)
#         at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:139)
#         at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)
#         at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74)
#         at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343)
#         at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:408)
#         at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66)
#         at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:860)
#         at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1598)
#         at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
#         at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
#         at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
#         at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
#         at java.lang.Thread.run(Thread.java:748)

阻塞分析:

  • 阻塞特征:线程状态为 BLOCKED,等待获取锁
  • 常见原因:锁竞争、数据库连接池耗尽、外部服务调用超时
  • 影响:导致请求响应缓慢,甚至超时

4. CPU 使用率分析

# 结合 top 命令分析 CPU 使用率高的线程
top -H -p 12345

# 输出示例:
#   PID USER      PR  NI    VIRT    RES    SHR S %CPU %MEM     TIME+ COMMAND
# 12345 java      20   0 1234567 123456  12345 R 95.2  2.3   0:15.23 java

# 然后使用 jstack 分析该线程
jstack 12345 | grep -A 20 "nid=0x1234"

CPU 使用率高的线程特征:

  • 线程状态为 RUNNABLE
  • 长时间占用 CPU,没有 I/O 等待
  • 可能存在死循环、复杂计算、频繁 GC

jmap(Java Memory Map)

jmap 是 JVM 自带的内存映射工具,用于生成 Java 进程的内存映射信息,包括堆内存使用情况、对象统计、类加载器信息等,是内存泄漏分析和性能调优的重要工具。

基本语法

jmap [options] <pid>

参数说明:

  • options:选项参数
  • pid:Java 进程 ID(通过 jps 获取)

常用选项

# 显示堆内存使用情况
jmap -heap 12345

# 显示堆内存详细信息
jmap -histo 12345

# 显示堆内存详细信息(包含对象数量)
jmap -histo:live 12345

# 导出堆转储文件(用于内存泄漏分析)
jmap -dump:format=b,file=heap.hprof 12345

# 导出堆转储文件(只包含存活对象)
jmap -dump:live,format=b,file=heap_live.hprof 12345

# 显示类加载器信息
jmap -clstats 12345

# 显示 finalizer 队列信息
jmap -finalizerinfo 12345

输出示例及分析

1. 堆内存使用情况

jmap -heap 12345

# 输出示例:
# Attaching to process ID 12345, please wait...
# Debugger attached successfully.
# Server compiler detected.
# JVM version is 25.291-b10
#
# using thread-local object allocation.
# Parallel GC with 4 thread(s)
#
# Heap Configuration:
#    MinHeapFreeRatio         = 40
#    MaxHeapFreeRatio         = 70
#    MaxHeapSize              = 1073741824 (1024.0MB)
#    NewSize                  = 357892096 (341.3MB)
#    MaxNewSize               = 357892096 (341.3MB)
#    OldSize                  = 715849728 (682.7MB)
#    NewRatio                 = 2
#    SurvivorRatio            = 8
#    MetaspaceSize            = 218103808 (208.0MB)
#    CompressedClassSpaceSize = 1073741824 (1024.0MB)
#    MaxMetaspaceSize         = 17592186044415 MB
#    G1HeapRegionSize         = 0 (0.0MB)
#
# Heap Usage:
# PS Young Generation
# Eden Space:
#    capacity = 268435456 (256.0MB)
#    used     = 134217728 (128.0MB)
#    free     = 134217728 (128.0MB)
#    50.0% used
# From Space:
#    capacity = 44736512 (42.7MB)
#    used     = 0 (0.0MB)
#    free     = 44736512 (42.7MB)
#    0.0% used
# To Space:
#    capacity = 44736512 (42.7MB)
#    used     = 0 (0.0MB)
#    free     = 44736512 (42.7MB)
#    0.0% used
# PS Old Generation
#    capacity = 715849728 (682.7MB)
#    used     = 357924864 (341.3MB)
#    free     = 357924864 (341.3MB)
#    50.0% used
#
# 1234 interned Strings occupying 123456 bytes.

输出解释:

  • Heap Configuration:堆配置信息

    • MinHeapFreeRatio/MaxHeapFreeRatio:堆空闲比例范围
    • MaxHeapSize:最大堆大小
    • NewSize/MaxNewSize:年轻代大小
    • OldSize:老年代大小
    • NewRatio:年轻代与老年代比例
    • SurvivorRatio:Eden 与 Survivor 比例
    • MetaspaceSize:元空间大小
    • CompressedClassSpaceSize:压缩类空间大小
  • Heap Usage:堆使用情况

    • capacity:容量
    • used:已使用
    • free:空闲
    • 使用率百分比

异常参数:

  • Eden Space used > 90%:Eden 区使用率过高,GC 频繁
  • Old Generation used > 85%

实战监控案例

案例 1:内存泄漏排查

症状:应用运行一段时间后内存使用率持续上升,最终 OOM

排查步骤:

  1. 使用 jstat 观察内存变化趋势
jstat -gcutil pid 1000 10
  1. 使用 jmap 导出堆转储文件
jmap -dump:format=b,file=leak.hprof 12345
  1. 使用 MAT 分析堆转储文件,查找内存泄漏对象

解决方案:

  • 修复代码中的内存泄漏点
  • 增加堆内存大小
  • 优化对象生命周期管理

脚本输出示例:

jstat -gcutil 12345 1000 10
# S0     S1     E      O      M     CCS    YGC    YGCT    FGC    FGCT     GCT
#  0.00   0.00  95.00  85.00  91.40  75.00    50   1.200    5    2.100    3.300
#  0.00   0.00  98.00  90.00  92.00  76.00    51   1.250    5    2.100    3.350

输出解释:

  • E:Eden 区使用率(%),持续高于 90% 说明对象无法被及时回收
  • O:老年代使用率(%),持续升高说明老年代对象不断累积
  • FGC:Full GC 次数,频繁 Full GC 但内存未释放说明有泄漏

异常参数:

  • O > 85% 且持续升高
  • FGC 频繁但 O 不下降

案例 2:CPU 使用率过高排查

症状:应用 CPU 使用率持续在 80% 以上,响应缓慢

排查步骤:

  1. 使用 top 命令找到高 CPU 进程
top -p 12345
  1. 使用 top -H 找到高 CPU 线程
top -H -p 12345
  1. 将线程 ID 转换为十六进制
printf "%x\n" 1234
# 输出:4d2
  1. 使用 jstack 分析线程堆栈
jstack 12345 | grep -A 20 "nid=0x4d2"

常见原因及解决方案:

  • 死循环:检查循环条件,添加循环退出机制
  • 复杂计算:优化算法,使用缓存,考虑异步处理
  • 频繁 GC:调整 GC 参数,优化内存分配
  • 线程竞争:减少锁竞争,使用无锁数据结构

脚本输出示例:

top -H -p 12345
#   PID USER      PR  NI    VIRT    RES    SHR S %CPU %MEM     TIME+ COMMAND
# 12345 java      20   0 1234567 123456  12345 R 95.2  2.3   0:15.23 java
# 12346 java      20   0 1234567 123456  12345 R 90.0  2.3   0:14.00 java

输出解释:

  • %CPU:线程占用 CPU 百分比,持续高于 80% 为异常
  • PID:可用于后续 jstack 分析

异常参数:

  • 某线程 %CPU 长时间 > 80%
  • 多个线程同时高 CPU

案例 3:线程死锁排查

症状:应用无响应,线程池耗尽,请求超时

排查步骤:

  1. 使用 jstack 检测死锁
jstack -l 12345
  1. 查找死锁信息
jstack -l 12345 | grep -A 50 "Found.*deadlock"

死锁示例代码:

public class DeadlockExample {
    private final Object lock1 = new Object();
    private final Object lock2 = new Object();

    public void methodA() {
        synchronized (lock1) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (lock2) {
                System.out.println("Method A executed");
            }
        }
    }

    public void methodB() {
        synchronized (lock2) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (lock1) {
                System.out.println("Method B executed");
            }
        }
    }
}

解决方案:

  • 统一锁的获取顺序
  • 使用 ReentrantLock 的 tryLock() 方法
  • 减少锁的粒度,使用分段锁
  • 使用无锁数据结构

脚本输出示例:

jstack -l 12345
# Found one Java-level deadlock:
# =============================
# "Thread-1":
#   waiting to lock monitor 0x00007f8c4c123456 (object 0x00000000d1234567, a java.lang.Object),
#   which is held by "Thread-2"
# "Thread-2":
#   waiting to lock monitor 0x00007f8c4c123789 (object 0x00000000d1234568, a java.lang.Object),
#   which is held by "Thread-1"

输出解释:

  • Found one Java-level deadlock:检测到死锁
  • waiting to lock:线程正在等待另一个线程持有的锁

异常参数:

  • 出现 deadlock 关键字
  • 多个线程互相等待锁资源

案例 4:GC 频繁导致性能下降

症状:应用响应时间不稳定,GC 日志显示频繁的 Young GC

排查步骤:

  1. 使用 jstat 监控 GC 情况
jstat -gc 12345 1000 10
  1. 启用 GC 日志
-XX:+PrintGCDetails -XX:+PrintGCTimeStamps -Xloggc:gc.log
  1. 分析 GC 日志
# GC 日志示例
2024-01-15T10:30:45.123+0800: [GC (Allocation Failure) [PSYoungGen: 262144K->32768K(305664K)] 262144K->32768K(1005056K), 0.050 secs] [Times: user=0.20 sys=0.00, real=0.05 secs]

异常指标:

  • Young GC 频率 > 1 次/秒
  • GC 耗时 > 100ms
  • GC 后内存回收率 < 50%

解决方案:

  • 增加年轻代大小:-Xmn512m
  • 调整 Survivor 比例:-XX:SurvivorRatio=8
  • 使用 G1GC:-XX:+UseG1GC
  • 优化对象分配,减少临时对象创建

脚本输出示例:

jstat -gc 12345 1000 10
#  S0C    S1C    S0U    S1U      EC       EU        OC         OU       MC     MU    CCSC   CCSU   YGC     YGCT    FGC    FGCT     GCT
# 512.0  512.0   0.0    0.0    2048.0   2047.0    4096.0     2048.0   4480.0 4096.0  512.0  384.0     50    1.200     2    0.800    2.000
# 512.0  512.0   0.0    0.0    2048.0   2047.0    4096.0     2048.0   4480.0 4096.0  512.0  384.0     51    1.250     2    0.800    2.050

输出解释:

  • YGC:年轻代 GC 次数,短时间内快速增长说明 GC 频繁
  • YGCT:年轻代 GC 总耗时,耗时过高影响性能
  • EU/EC:Eden 区使用率,接近 100% 说明 Eden 区频繁被填满

异常参数:

  • YGC > 1 次/秒
  • YGCT > 100ms/次
  • EU/EC > 0.95(Eden 区使用率过高)

案例 5:Full GC 频繁导致应用暂停

症状:应用偶尔出现长时间暂停,用户体验差

排查步骤:

  1. 监控 Full GC 频率
jstat -gcutil 12345 1000 10
  1. 分析 Full GC 原因
# 查看 GC 日志中的 Full GC 信息
grep "Full GC" gc.log

常见原因:

  • 老年代内存不足
  • 内存泄漏导致老年代对象无法回收
  • 大对象直接进入老年代

解决方案:

  • 增加堆内存:-Xmx4g
  • 调整老年代比例:-XX:NewRatio=3
  • 使用 G1GC 减少暂停时间
  • 排查并修复内存泄漏

脚本输出示例:

grep "Full GC" gc.log
# 2024-01-15T10:31:12.456+0800: [Full GC (Ergonomics) [PSYoungGen: 32768K->0K(305664K)] [ParOldGen: 682666K->341333K(682666K)] 715434K->341333K(988330K), [Metaspace: 123456K->123456K(123456K)], 0.8000000 secs] [Times: user=1.20 sys=0.00, real=0.80 secs]

输出解释:

  • Full GC:标识为 Full GC
  • 0.8000000 secs:本次 Full GC 耗时,超过 0.5 秒为异常
  • ParOldGen: 682666K->341333K:老年代回收前后内存变化

异常参数:

  • Full GC 耗时 > 0.5 秒
  • Full GC 频率 > 1 次/5 分钟
  • Full GC 后老年代回收率 < 50%

案例 6:类加载器泄漏排查

症状:元空间使用率持续增长,最终 OOM

排查步骤:

  1. 监控类加载情况
jstat -class 12345 1000 10
  1. 查看类加载器信息
jmap -clstats 12345
  1. 分析类加载器泄漏
jmap -dump:format=b,file=classloader.hprof 12345

常见原因:

  • 动态代理类未正确卸载
  • 反射生成的类未清理
  • 自定义类加载器未正确关闭

解决方案:

  • 及时清理动态代理对象
  • 使用弱引用管理反射对象
  • 正确关闭自定义类加载器
  • 增加元空间大小:-XX:MaxMetaspaceSize=512m

脚本输出示例:

jstat -class 12345 1000 10
# Loaded  Bytes  Unloaded  Bytes     Time
#   1234  2048.0       56   128.0       0.05
#   2234  3048.0      156   228.0       0.10
jmap -clstats 12345
# class_loader    classes bytes parent_loader alive? type
# 0x00000000d1234567  1000   2048000 0x00000000d1234000  true  java/lang/ClassLoader
# 0x00000000d1234568  500    1024000 0x00000000d1234000  true  java/lang/ClassLoader

输出解释:

  • Loaded:已加载类数量,持续增长说明类未被卸载
  • Unloaded:已卸载类数量,过低说明类加载器未回收
  • class_loader:类加载器对象,数量异常多说明泄漏

异常参数:

  • Loaded 持续增长且 Unloaded 很少
  • 类加载器对象数量异常多
  • 元空间(Metaspace)使用率持续升高

案例 7:线程池耗尽排查

脚本输出示例:

jstack 12345 | grep -E "(pool|executor)" -A 5
# "taskExecutor-1" #23 prio=5 os_prio=0 tid=0x00007f8c4c123456 nid=0x1239 waiting on condition [0x00007f8c4d456000]
#    java.lang.Thread.State: WAITING (parking)
#         at sun.misc.Unsafe.park(Native Method)
#         at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
#         at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1067)
#         at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1127)

输出解释:

  • Thread.State: WAITING (parking):线程处于等待任务状态
  • ThreadPoolExecutor.getTask:线程池队列已满,线程在等待新任务

异常参数:

  • 线程池中大量线程处于 WAITING 状态
  • 队列长度接近或等于最大容量
  • 任务堆积,响应超时

案例 8:数据库连接池耗尽排查

脚本输出示例:

jstack 12345 | grep -E "(mysql|postgres|oracle)" -A 10
# "HikariPool-1 housekeeper" #45 prio=5 os_prio=0 tid=0x00007f8c4c123789 nid=0x1240 waiting on condition [0x00007f8c4d789000]
#    java.lang.Thread.State: WAITING (on object monitor)
#         at java.lang.Object.wait(Native Method)
#         at com.zaxxer.hikari.pool.HikariPool$HouseKeeper.run(HikariPool.java:1234)
#         ...

输出解释:

  • Thread.State: WAITING (on object monitor):线程在等待数据库连接释放
  • HikariPool$HouseKeeper:连接池维护线程,可能因无可用连接而阻塞

异常参数:

  • 连接池线程长时间等待
  • 活跃连接数接近最大池大小
  • 数据库响应慢或超时

案例 9:内存碎片化排查

脚本输出示例:

jmap -heap 12345
# PS Old Generation
#    capacity = 715849728 (682.7MB)
#    used     = 341333333 (325.6MB)
#    free     = 374516395 (357.1MB)
#    50% used

输出解释:

  • capacity:老年代总容量
  • used:已用内存
  • free:空闲内存
  • used 占比不高但频繁 Full GC,说明内存碎片化严重

异常参数:

  • 老年代 used < 70% 但频繁 Full GC
  • 大量小对象分布,回收效果差

案例 10:网络 I/O 阻塞排查

脚本输出示例:

jstack 12345 | grep -E "(http|nio|socket)" -A 10
# "http-nio-8080-exec-1" #23 daemon prio=5 os_prio=0 tid=0x00007f8c4c123456 nid=0x1239 waiting on condition [0x00007f8c4d456000]
#    java.lang.Thread.State: WAITING (on object monitor)
#         at java.net.SocketInputStream.socketRead0(Native Method)
#         at java.net.SocketInputStream.socketRead(SocketInputStream.java:116)
#         ...

输出解释:

  • Thread.State: WAITING (on object monitor):线程在等待网络 I/O
  • SocketInputStream.socketRead:同步 I/O 阻塞,线程无法继续执行

异常参数:

  • 大量线程处于 WAITING 状态
  • 网络 I/O 调用堆栈频繁出现

案例 11:缓存穿透导致性能问题

脚本输出示例:

tail -f cache.log | grep "MISS"
# [2024-01-15 10:32:00] cache MISS: user:123
# [2024-01-15 10:32:01] cache MISS: user:124

输出解释:

  • cache MISS:缓存未命中,频繁 MISS 说明缓存穿透

异常参数:

  • 缓存命中率 < 80%
  • 相同 key 频繁 MISS
  • 数据库压力异常升高

案例 12:JVM 参数配置不当

脚本输出示例:

jcmd 12345 VM.flags
# -Xms1g -Xmx1g -XX:NewRatio=1 -XX:SurvivorRatio=2 -XX:+UseSerialGC -XX:MaxMetaspaceSize=128m

输出解释:

  • -Xms/-Xmx:堆内存过小
  • -XX:NewRatio、-XX:SurvivorRatio:比例不合理
  • -XX:+UseSerialGC:单线程 GC,性能差
  • -XX:MaxMetaspaceSize:元空间过小

异常参数:

  • 堆内存 < 2G
  • 年轻代/老年代比例极端
  • Survivor 区比例过小
  • 使用 SerialGC
  • 元空间 < 256M

案例 13:应用启动慢排查

脚本输出示例:

tail -f application.log | grep -E "(Started|Initialized)"
# 2024-01-15 10:33:00 Application Started in 35.123 seconds

输出解释:

  • Started in xx seconds:启动耗时,超过 30 秒为异常

异常参数:

  • 启动时间 > 30 秒
  • 类加载数 > 10000
  • 启动期间 GC 频繁

案例 14:内存使用率突增排查

脚本输出示例:

jstat -gcutil 12345 1000 5
# S0     S1     E      O      M     CCS    YGC    YGCT    FGC    FGCT     GCT
#  0.00   0.00  95.00  85.00  91.40  75.00    50   1.200    5    2.100    3.300
#  0.00   0.00  98.00  90.00  92.00  76.00    51   1.250    5    2.100    3.350

输出解释:

  • E、O:Eden 区、老年代使用率短时间内急剧上升
  • YGC、FGC:GC 次数短时间内激增

异常参数:

  • 内存使用率短时间内 > 90%
  • GC 次数短时间内激增

案例 15:应用响应时间不稳定

脚本输出示例:

jstat -gcutil 12345 1000 10
# GCT
# 0.150
# 0.300
# 0.800

输出解释:

  • GCT:GC 总耗时,波动大说明 GC 影响响应

异常参数:

  • GC 总耗时波动大
  • 线程状态频繁切换(RUNNABLE/BLOCKED/WAITING)
  • 系统资源利用率波动大

最近更新:: 2025/6/26 09:11
Contributors: Duke
Prev
JVM垃圾回收
Next
JVM调优