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

20 阅读5分钟

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

本章目标

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

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

很多开发者学习 JVM 时:

知道GC原理
知道G1
知道ZGC

但到了线上:

CPU 100%
频繁Full GC
接口超时
服务卡死
OOM

却不知道如何排查。

实际上:

面试中的 JVM

关注:

GC算法
垃圾收集器
JMM

工作中的 JVM

关注:

为什么Full GC?
为什么OOM?
为什么CPU飙高?
为什么接口变慢?

真正值钱的是:

线上问题排查能力

二、JVM 调优核心原则

先记住一句话:

没有最优参数
只有最适合业务的参数

例如:

电商系统:

低延迟优先

推荐:

G1
ZGC

批处理系统:

吞吐量优先

推荐:

Parallel GC

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

查看 Java 进程:

jps -l

输出:

12345 com.company.OrderApplication
67890 Jps

说明:

12345

就是目标进程 PID。


四、查看 JVM 基本信息

查看启动参数:

jinfo 12345

查看:

-Xms
-Xmx
GC配置
系统属性

查看 JVM 状态:

jstat -gc 12345 1000

每秒输出一次:

S0C
S1C
EC
OC
YGC
FGC

五、GC日志是什么?

GC 日志记录:

什么时候GC
回收了多少内存
暂停了多久

例如:

[GC pause (G1 Evacuation Pause)
 200M->50M(1024M)
 10ms]

含义:

GC前:200M

GC后:50M

总堆:1024M

耗时:10ms

六、开启 GC 日志

JDK8:

-XX:+PrintGCDetails
-XX:+PrintGCDateStamps
-Xloggc:gc.log

JDK9+:

-Xlog:gc*:file=gc.log

生成:

gc.log

七、GC日志怎么看?

示例:

[GC (Allocation Failure)
 256M->32M(512M),
 0.010s]

分析:

256M
GC前使用

32M
GC后使用

512M
总堆大小

10ms
GC耗时

重点关注:

GC频率
GC耗时
Full GC次数

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

例如:

每分钟Minor GC几次

正常。


如果:

每秒Minor GC

说明:

对象创建过快

如果:

频繁Full GC

通常:

内存配置不合理

或者:

内存泄漏

九、GC问题排查口诀

看到频繁GC:

先问:

对象太多?
内存太小?
泄漏?

不要直接改参数。


十、线上最常见问题:OOM

经典异常:

java.lang.OutOfMemoryError

OOM并不只有一种。


十一、Heap OOM

最常见。

异常:

java.lang.OutOfMemoryError:
Java heap space

示例:

List<byte[]> list =
        new ArrayList<>();

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

持续占用堆。

最终:

Heap OOM

十二、Metaspace OOM

异常:

OutOfMemoryError:
Metaspace

常见原因:

动态生成大量类

例如:

CGLIB
ByteBuddy
ASM

十三、栈溢出

异常:

StackOverflowError

示例:

public void test() {
    test();
}

无限递归。


最终:

StackOverflowError

十四、Direct Memory OOM

异常:

Direct buffer memory

示例:

ByteBuffer.allocateDirect(
        1024 * 1024);

大量创建。


常见于:

Netty
NIO

十五、OOM 排查第一步:Dump

生产环境必须开启:

-XX:+HeapDumpOnOutOfMemoryError

发生OOM:

自动生成:

heap.hprof

文件。


十六、Heap Dump 是什么?

Heap Dump:

某一时刻
整个堆内存快照

包含:

所有对象
引用关系
对象大小

十七、查看堆情况

命令:

jmap -heap PID

查看:

堆大小

GC配置

各区域使用率

查看对象统计:

jmap -histo PID

输出:

num
instances
bytes
class name

例如:

1:
500000
40000000
java.lang.String

说明:

String过多

十八、MAT 工具分析 Dump

MAT:

Memory Analyzer Tool

JVM排查神器。


打开:

heap.hprof

重点看:

Leak Suspects

例如:

static HashMap

引用:

200万对象

立即发现问题。


十九、经典内存泄漏案例

错误代码:

public class Cache {

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

}

业务不断:

USERS.add(user);

由于:

static变量

始终是 GC Root。


导致:

永远不会回收

最终:

OOM

二十、CPU 100% 如何排查?

很多线上事故:

CPU直接100%

第一步:

top -Hp PID

查看线程。

输出:

12345

线程ID。


转16进制:

printf "%x\n" 12345

例如:

3039

二十一、查看线程堆栈

命令:

jstack PID > stack.log

搜索:

nid=0x3039

找到问题线程。


例如:

while(true){

}

导致:

死循环

CPU100%。


二十二、死锁排查

代码:

synchronized(lock1){

    synchronized(lock2){

    }

}

另一个线程:

synchronized(lock2){

    synchronized(lock1){

    }

}

形成:

DeadLock

jstack:

jstack PID

直接输出:

Found one Java-level deadlock

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

现象:

接口越来越慢

监控:

Full GC
每分钟50

jstat:

jstat -gc PID

发现:

Old区一直增长

Dump分析:

Order对象
300

继续分析:

static Map<Long, Order>

缓存未清理。


解决:

Guava Cache

Redis

定时清理

问题解决。


二十四、GC 调优常见参数


设置初始堆:

-Xms4g

最大堆:

-Xmx4g

推荐:

Xms = Xmx

避免动态扩容。


G1停顿时间:

-XX:MaxGCPauseMillis=200

开启Dump:

-XX:+HeapDumpOnOutOfMemoryError

Dump路径:

-XX:HeapDumpPath=/data/dump

二十五、线上 JVM 排查流程

记住这张图。


出现问题:

服务变慢

查看:

top

CPU高?

YES

jstack

分析线程。


CPU正常?

查看:

jstat -gc

分析GC。


GC频繁?

jmap -histo

查看对象。

MAT

分析Dump。

定位泄漏对象。


二十六、面试高频题

Full GC频繁怎么办?

先看:

GC日志

再分析:

老年代对象

而不是直接调参数。


如何定位OOM?

步骤:

开启Dump

生成hprof

MAT分析

找到大对象

CPU 100% 怎么查?

步骤:

top

top -Hp

jstack

定位线程

jstack 有什么用?

查看:

线程状态

死锁

阻塞

MAT 最常看什么?

Leak Suspects

Dominator Tree

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

8G服务器

-server

-Xms4g
-Xmx4g

-XX:+UseG1GC

-XX:MaxGCPauseMillis=200

-XX:+HeapDumpOnOutOfMemoryError

-XX:HeapDumpPath=/data/dump

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

本章总结

线上 JVM 排查核心思路:

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

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

  • 内存结构
  • GC 原理
  • 垃圾收集器
  • JVM 调优
  • 线上故障排查