第六章:JVM 调优实战 —— GC日志分析、内存溢出排查与线上问题定位

第六章:JVM 调优实战 ------ GC日志分析、内存溢出排查与线上问题定位

本章目标

  • 掌握 JVM 线上问题排查思路
  • 学会分析 GC 日志
  • 学会定位 OOM(内存溢出)
  • 掌握 jps、jstat、jmap、jstack 等工具
  • 学会分析 Heap Dump
  • 掌握生产环境 JVM 调优方法

一、为什么要学习 JVM 调优?

很多开发者学习 JVM 时:

复制代码
知道GC原理
知道G1
知道ZGC

但到了线上:

sql 复制代码
CPU 100%
频繁Full GC
接口超时
服务卡死
OOM

却不知道如何排查。

实际上:

面试中的 JVM

关注:

复制代码
GC算法
垃圾收集器
JMM

工作中的 JVM

关注:

sql 复制代码
为什么Full GC?
为什么OOM?
为什么CPU飙高?
为什么接口变慢?

真正值钱的是:

复制代码
线上问题排查能力

二、JVM 调优核心原则

先记住一句话:

复制代码
没有最优参数
只有最适合业务的参数

例如:

电商系统:

复制代码
低延迟优先

推荐:

复制代码
G1
ZGC

批处理系统:

复制代码
吞吐量优先

推荐:

复制代码
Parallel GC

三、线上排查第一步:确认 JVM 进程

查看 Java 进程:

复制代码
jps -l

输出:

复制代码
12345 com.company.OrderApplication
67890 Jps

说明:

复制代码
12345

就是目标进程 PID。


四、查看 JVM 基本信息

查看启动参数:

复制代码
jinfo 12345

查看:

diff 复制代码
-Xms
-Xmx
GC配置
系统属性

查看 JVM 状态:

yaml 复制代码
jstat -gc 12345 1000

每秒输出一次:

复制代码
S0C
S1C
EC
OC
YGC
FGC

五、GC日志是什么?

GC 日志记录:

复制代码
什么时候GC
回收了多少内存
暂停了多久

例如:

csharp 复制代码
[GC pause (G1 Evacuation Pause)
 200M->50M(1024M)
 10ms]

含义:

复制代码
GC前:200M

GC后:50M

总堆:1024M

耗时:10ms

六、开启 GC 日志

JDK8:

ruby 复制代码
-XX:+PrintGCDetails
-XX:+PrintGCDateStamps
-Xloggc:gc.log

JDK9+:

ruby 复制代码
-Xlog:gc*:file=gc.log

生成:

c 复制代码
gc.log

七、GC日志怎么看?

示例:

csharp 复制代码
[GC (Allocation Failure)
 256M->32M(512M),
 0.010s]

分析:

复制代码
256M
GC前使用

32M
GC后使用

512M
总堆大小

10ms
GC耗时

重点关注:

sql 复制代码
GC频率
GC耗时
Full GC次数

八、什么样的GC是健康的?

例如:

复制代码
每分钟Minor GC几次

正常。


如果:

复制代码
每秒Minor GC

说明:

复制代码
对象创建过快

如果:

sql 复制代码
频繁Full GC

通常:

复制代码
内存配置不合理

或者:

复制代码
内存泄漏

九、GC问题排查口诀

看到频繁GC:

先问:

复制代码
对象太多?
内存太小?
泄漏?

不要直接改参数。


十、线上最常见问题:OOM

经典异常:

复制代码
java.lang.OutOfMemoryError

OOM并不只有一种。


十一、Heap OOM

最常见。

异常:

makefile 复制代码
java.lang.OutOfMemoryError:
Java heap space

示例:

csharp 复制代码
List<byte[]> list =
        new ArrayList<>();

while(true){
    list.add(new byte[1024*1024]);
}

持续占用堆。

最终:

复制代码
Heap OOM

十二、Metaspace OOM

异常:

makefile 复制代码
OutOfMemoryError:
Metaspace

常见原因:

复制代码
动态生成大量类

例如:

objectivec 复制代码
CGLIB
ByteBuddy
ASM

十三、栈溢出

异常:

复制代码
StackOverflowError

示例:

csharp 复制代码
public void test() {
    test();
}

无限递归。


最终:

复制代码
StackOverflowError

十四、Direct Memory OOM

异常:

arduino 复制代码
Direct buffer memory

示例:

yaml 复制代码
ByteBuffer.allocateDirect(
        1024 * 1024);

大量创建。


常见于:

复制代码
Netty
NIO

十五、OOM 排查第一步:Dump

生产环境必须开启:

ruby 复制代码
-XX:+HeapDumpOnOutOfMemoryError

发生OOM:

自动生成:

复制代码
heap.hprof

文件。


十六、Heap Dump 是什么?

Heap Dump:

复制代码
某一时刻
整个堆内存快照

包含:

复制代码
所有对象
引用关系
对象大小

十七、查看堆情况

命令:

复制代码
jmap -heap PID

查看:

复制代码
堆大小

GC配置

各区域使用率

查看对象统计:

复制代码
jmap -histo PID

输出:

kotlin 复制代码
num
instances
bytes
class name

例如:

arduino 复制代码
1:
500000
40000000
java.lang.String

说明:

arduino 复制代码
String过多

十八、MAT 工具分析 Dump

MAT:

复制代码
Memory Analyzer Tool

JVM排查神器。


打开:

复制代码
heap.hprof

重点看:

复制代码
Leak Suspects

例如:

arduino 复制代码
static HashMap

引用:

复制代码
200万对象

立即发现问题。


十九、经典内存泄漏案例

错误代码:

swift 复制代码
public class Cache {

    private static final List<User>
            USERS = new ArrayList<>();

}

业务不断:

csharp 复制代码
USERS.add(user);

由于:

arduino 复制代码
static变量

始终是 GC Root。


导致:

复制代码
永远不会回收

最终:

复制代码
OOM

二十、CPU 100% 如何排查?

很多线上事故:

erlang 复制代码
CPU直接100%

第一步:

css 复制代码
top -Hp PID

查看线程。

输出:

复制代码
12345

线程ID。


转16进制:

perl 复制代码
printf "%x\n" 12345

例如:

yaml 复制代码
3039

二十一、查看线程堆栈

命令:

c 复制代码
jstack PID > stack.log

搜索:

ini 复制代码
nid=0x3039

找到问题线程。


例如:

arduino 复制代码
while(true){

}

导致:

复制代码
死循环

CPU100%。


二十二、死锁排查

代码:

javascript 复制代码
synchronized(lock1){

    synchronized(lock2){

    }

}

另一个线程:

javascript 复制代码
synchronized(lock2){

    synchronized(lock1){

    }

}

形成:

复制代码
DeadLock

jstack:

复制代码
jstack PID

直接输出:

sql 复制代码
Found one Java-level deadlock

二十三、频繁 Full GC 排查案例

现象:

复制代码
接口越来越慢

监控:

sql 复制代码
Full GC
每分钟50次

jstat:

复制代码
jstat -gc PID

发现:

sql 复制代码
Old区一直增长

Dump分析:

css 复制代码
Order对象
300万

继续分析:

vbnet 复制代码
static Map<Long, Order>

缓存未清理。


解决:

复制代码
Guava Cache

Redis

定时清理

问题解决。


二十四、GC 调优常见参数


设置初始堆:

diff 复制代码
-Xms4g

最大堆:

diff 复制代码
-Xmx4g

推荐:

ini 复制代码
Xms = Xmx

避免动态扩容。


G1停顿时间:

ini 复制代码
-XX:MaxGCPauseMillis=200

开启Dump:

ruby 复制代码
-XX:+HeapDumpOnOutOfMemoryError

Dump路径:

ruby 复制代码
-XX:HeapDumpPath=/data/dump

二十五、线上 JVM 排查流程

记住这张图。


出现问题:

复制代码
服务变慢

查看:

css 复制代码
top

CPU高?

objectivec 复制代码
YES

复制代码
jstack

分析线程。


CPU正常?

查看:

复制代码
jstat -gc

分析GC。


GC频繁?

复制代码
jmap -histo

查看对象。

复制代码
MAT

分析Dump。

定位泄漏对象。


二十六、面试高频题

Full GC频繁怎么办?

先看:

复制代码
GC日志

再分析:

复制代码
老年代对象

而不是直接调参数。


如何定位OOM?

步骤:

复制代码
开启Dump

生成hprof

MAT分析

找到大对象

CPU 100% 怎么查?

步骤:

css 复制代码
top

top -Hp

jstack

定位线程

jstack 有什么用?

查看:

复制代码
线程状态

死锁

阻塞

MAT 最常看什么?

复制代码
Leak Suspects

Dominator Tree

二十七、生产环境 JVM 参数模板

8G服务器

ruby 复制代码
-server

-Xms4g
-Xmx4g

-XX:+UseG1GC

-XX:MaxGCPauseMillis=200

-XX:+HeapDumpOnOutOfMemoryError

-XX:HeapDumpPath=/data/dump

-Xlog:gc*:file=/data/logs/gc.log

本章总结

线上 JVM 排查核心思路:

objectivec 复制代码
服务异常
    │
    ▼
CPU高?
    │
    ├── YES
    │      ▼
    │   jstack
    │
    └── NO
            ▼
         GC频繁?
            │
            ▼
          jstat
            │
            ▼
         Full GC?
            │
            ▼
          Dump
            │
            ▼
           MAT
            │
            ▼
        找到泄漏对象

到这里,你已经掌握了 JVM 的:

  • 内存结构
  • GC 原理
  • 垃圾收集器
  • JVM 调优
  • 线上故障排查
相关推荐
SuniaWang1 小时前
《AgentX 专栏》08-工作流引擎:AgentWorkflow怎么把工具记忆流程串成一条流水线
java·ai·架构·langchain·工作流引擎·langgraph·agent架构
SXJR1 小时前
langchain4j是如何保证tools或者funcation call不出错的
java·网络·数据库·ai·语言模型
子一!!1 小时前
spring基础学习
java·学习·spring
拽着尾巴的鱼儿1 小时前
Java 对象的深拷贝和浅拷贝
java·开发语言
我不是懒洋洋2 小时前
手写一个异步日志库:从printf到高性能无锁日志
java·c语言·开发语言·c++·visual studio
李少兄2 小时前
Java 工程化基石:标准目录结构与 META-INF 元信息机制
java·开发语言
就叫_这个吧2 小时前
理解Java反射机制和内省机制应用与实践
java·开发语言·反射
未若君雅裁2 小时前
synchronized 底层原理:Monitor、对象头、Mark Word 与锁升级
java
m0_752035633 小时前
markdown语言格式
java