线上 Full GC 故障模拟

  1. 演练目标

    模拟一个"内存泄漏导致频繁 Full GC"的场景,并完成从发现、定位到取证的全过程。

    环境配置:4GB 堆内存,G1 垃圾回收器。

    故障现象:CPU 飙高,应用响应卡顿,GC 日志疯狂刷屏,但进程未崩溃(假死)。

  2. 准备工作:制造故障

    2.1 编写模拟代码

    创建文件 FullGCPressure.java。该代码会持续填充堆内存至 80% 以上,并反复震荡,迫使 G1 收集器陷入"清理-失败-清理"的死循环。

java 复制代码
java
复制
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;

public class FullGCPressure {
    // 静态容器 (GC Root),模拟内存泄漏
    private static final List<byte[]> LEAK_CONTAINER = new ArrayList<>();
    
    public static void main(String[] args) {
        System.out.println("=== Full GC 施压程序启动 (Heap: 4GB) ===");
        // 目标:占用 3.2GB (4GB * 80%)
        long TARGET_SIZE = 3200L * 1024 * 1024;
        long currentSize = 0;

        try {
            while (true) {
                // 1. 申请 1MB 内存 (使用随机数填充,防止操作系统 Zero Page 优化)
                byte[] data = new byte[1024 * 1024];
                data[0] = (byte) ThreadLocalRandom.current().nextInt();
                
                LEAK_CONTAINER.add(data);
                currentSize += (1024 * 1024);

                // 2. 达到阈值后,微量清理,诱发 GC 震荡
                if (currentSize > TARGET_SIZE) {
                    // 移除 100MB 旧数据,给 GC 一点希望,防止直接 OOM
                    int freeCount = 100;
                    for (int i = 0; i < freeCount; i++) {
                        if (!LEAK_CONTAINER.isEmpty()) {
                            LEAK_CONTAINER.remove(0);
                        }
                    }
                    currentSize -= (freeCount * 1024L * 1024L);
                    
                    // 稍微停顿,让 GC 线程抢占 CPU
                    TimeUnit.MILLISECONDS.sleep(50);
                }
                // 无 sleep 极速分配,压榨 CPU
            }
        } catch (OutOfMemoryError e) {
            System.out.println("OOM 崩溃,模拟结束");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

2.2 编译与启动

使用符合生产环境的参数启动(注意:假设当前用户为 robin)。

java 复制代码
# 1. 编译
javac FullGCPressure.java
# 2. 启动 (指定 G1GC, 4GB 堆, 打印详细日志)
/usr/local/java8/bin/java -server \
  -Xms4g -Xmx4g \
  -XX:+UseG1GC \
  -XX:MaxGCPauseMillis=100 \
  -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCTimeStamps \
  -Xloggc:gc.log \
  FullGCPressure &

/usr/local/java8/bin/java: 谁来跑? (Java 虚拟机程序)

-server -Xms4g ... -Xloggc:gc.log: 怎么跑? (配置内存、GC策略、日志路径等 JVM 参数)

FullGCPressure: 跑哪个? (告诉 JVM 加载这个类,并运行它的 main 方法)

&: 在哪跑? (在后台运行,不占用当前终端窗口)

3.排查实战:侦探工作 (The Detective)

3.1 确认故障现象

命令:top

现象:找到 java 进程(记下 PID,例如 8060),观察 %CPU 是否接近或超过 100%。

3.2 实锤 Full GC (关键步骤)

命令:jstat -gcutil <间隔ms>

注意:必须使用启动进程的用户身份执行,否则报错 Could not attach。

java 复制代码
# 假设 PID 为 8060,用户为 robin
sudo -u robin /usr/local/java8/bin/jstat -gcutil 8060 1000

图片就不贴上去了

观察指标:

O (Old): 长期维持在 99.xx%。

FGC (Full GC Count): 数字在不断增加 (561 -> 562 -> 563)。

FGCT (Full GC Time): 时间增长迅速,说明 CPU 都在做 GC。

4 取证环节:导出堆转储 (The Forensics)

这是最容易踩坑的环节(权限不足 + 磁盘空间不足)。

4.1 准备存储目录 (解决 "No space left on device")(如果内存够就不需要再去新建一个文件目录 )

由于根目录 / 通常空间较小,我们需要找一个大磁盘(如 /docker 或 /data)。

java 复制代码
# 1. 查看磁盘空间
df -h 
# 2. 创建目录 (假设 /docker 空间足够)
sudo mkdir -p /docker/dump_data
# 3. 修改目录权限
sudo chown 777 /docker/dump_data

4.2 执行 Dump (解决 "Permission denied")

使用 jmap 导出内存快照。

java 复制代码
# 导出
sudo -u  /usr/local/java8/bin/jmap -dump:format=b,file=/docker/dump_data/heap_dump.hprof 8060

注:如果进程卡死无响应,可加 -F 参数强制导出(速度慢)。

4.3 准备下载 :sftp 下载远程机器文件

java 复制代码
# 修改文件权限,允许其他人读取
sudo chmod 644 /docker/dump_data/heap_dump.hprof
5. 根因分析 (The Analysis)
5.1 下载文件
通过 SFTP 将 /docker/dump_data/heap_dump.hprof 下载到本地。

5.2 使用 MAT 分析

打开 Eclipse Memory Analyzer (MAT)。

加载 heap_dump.hprof。

点击 "Leak Suspects Report"。

结论:

报告会直接指出:java.util.ArrayList 占用了 80% 以上的堆内存。

查看引用链:发现是 FullGCPressure 类的 LEAK_CONTAINER 静态变量持有的。

对象内容:全是 byte\[\] 数组。

  1. 恢复环境 (Cleanup):演练结束后,务必清理现场,释放资源;或者调用stopFullGc就可以。
java 复制代码
# 1. 杀掉模拟进程
kill -9 8060
# 2. 删除巨大的 Dump 文件 (释放磁盘空间)
sudo rm -rf /docker/dump_data/heap_dump.hprof
# 3. 删除日志
rm gc.log
相关推荐
wuminyu39 分钟前
JVM规范学习总结:class执行性能差异
java·linux·c语言·jvm·c++
EVEN-091344 分钟前
Linux-线程控制
linux·jvm
刚入门的大一新生19 小时前
Linux-线程概念
linux·jvm
eleven_4u20 小时前
QPS如何计算,如何部署架构
java·linux·运维·jvm·中间件
Huanzhi_Lin1 天前
资源回收源码阅读参考-上
游戏开发·gc·laya·layaair·资源回收·laya3.4
qq_452396231 天前
第八篇:《VACUUM、自动清理与膨胀治理》
jvm·数据库·oracle
甜到心里的蛋糕1 天前
抖音评论自动回复机器人部署实践:无头 Chrome + DeepSeek + NapCat(2C2G 云主机)
java·jvm·chrome·python·抖音·deepseek
香蕉大香蕉(06敬畏市场)1 天前
高频八股 · JVM(7):方法区、元空间与字符串常量池
java·jvm·ai·博客·八股·大厂实习
wangchunyu1142 天前
JAVA JVM内存结构与运行时数据区
java·开发语言·jvm
骑着蜗牛撵大象3272 天前
AI Agent 工程化开发实战(三):用 StateGraph 给智能体搭一条可回放的状态流水线
java·开发语言·jvm·ai·状态管理·langgraph·stategraph