Java内存泄漏、CPU飙升排查

在Java应用开发中,内存泄漏和CPU飙升是两类高频出现的生产问题,也是常见的面试问题。这里通过一些demo进行实践。

内存泄漏

java 复制代码
private static List<byte[]> leakList = new ArrayList<>();

@GetMapping("/memory/leak")
public void test2() {
    try {
        while (true) {
            // 分配1MB的内存块
            byte[] block = new byte[1024 * 1024];
            leakList.add(block);

            // 每隔100毫秒分配一次,模拟内存不断积累
            Thread.sleep(100);
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
}
shell 复制代码
java -jar -Xms256m -Xmx256m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/Users/wx/workspace/app.hprof test.jar


jstat -gcutil <pid> 1000  # 观察Old区使用率持续上涨

-XX:+HeapDumpOnOutOfMemoryError: 在发生 OutOfMemoryError 时自动生成堆转储文件(Heap Dump) -XX:HeapDumpPath: 指定堆转储文件的保存位置,可以使用工具(如 MAT、JProfiler 等)打开和分析

可以通过jmap命令生成hprof报告

shell 复制代码
jmap -dump:live,format=b,file=heap_dump.bin <pid>

# 查看Java进程
jps -l
jcmd
ps -ef | grep java

使用JProfiler打开*.hprof文件,查看最大内存占有的数据类型

点击目标数据类型,选择incoming references

  • incoming references:查看目标数据类型被哪些对象引用了
  • outgoing references:查看目标数据类型引用了哪些对象

找到泄漏对象 LeakedObject由于被静态集合引用而无法被垃圾回收,从而确定位置

CPU飙升

java 复制代码
@GetMapping("/cpu")
public void test3() {
    CompletableFuture<Void> t1 = CompletableFuture.supplyAsync(() -> {
            while (true) {
                // 密集计算,无退出条件
                double result = 0;
                for (int i = 0; i < 100000; i++) {
                    result += Math.sqrt(i) * Math.tan(i);
                }
            }
        }, threadPoolExecutor);

        CompletableFuture<Void> t2 = CompletableFuture.supplyAsync(() -> {
            while (true) {
                try {
                    Thread.sleep(1000);
                    System.out.println("正常业务运行中...");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, threadPoolExecutor);
        CompletableFuture.allOf(t1, t2).join();
}
  1. 定位Java进程,top -c
  2. 查看线程级别CPU占用,top -Hp <PID>
  3. 抓去线程快照,jstack <PID> > thread.txt
  4. 线程ID转换printf "%x\n" <线程ID>
  5. 进行分析
txt 复制代码
"WXW-Thread-1" #35 daemon prio=5 os_prio=31 cpu=150071.37ms elapsed=150.75s tid=0x00007fdfb58fea00 nid=0x7207 runnable  [0x0000700006795000]
   java.lang.Thread.State: RUNNABLE
	at com.example.controller.TestThreadController.lambda$test3$2(TestThreadController.java:94)
	at java.util.concurrent.CompletableFuture$AsyncSupply.run(java.base@17.0.13/CompletableFuture.java:1768)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(java.base@17.0.13/ThreadPoolExecutor.java:1136)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(java.base@17.0.13/ThreadPoolExecutor.java:635)
	at java.lang.Thread.run(java.base@17.0.13/Thread.java:840)

通过日志查找问题所在

使用JProfiler分析CPU视图,定位异常线程和代码

内存结构

JVM(Java虚拟机)的内存结构是其运行时数据区域的核心组成部分,用于管理程序执行过程中的内存分配和回收

程序计数器

作用

记录当前线程执行的位置(字节码指令地址)

特性
  1. 线程私有,每个线程独立存储
  2. 唯一不会发生 OutOfMemoryError 的区域

虚拟机栈

作用

存储方法调用栈帧(每个方法对应一个栈帧),管理局部变量、操作数栈、动态链接和方法返回地址

栈帧结构
  • 局部变量表:存放方法参数和方法内定义的局部变量(基本类型和对象引用)
  • 操作数栈:执行自己饿吗指令的临时操作数存储区
  • 动态链接:指向运行时常量池中该方法的符号引用
  • 返回地址:方法退出后需要返回的位置
特点
  1. 线程私有
  2. 可能抛出的异常
    • StackOverflowError:栈深度超过限制(无限递归)
    • OutOfMemoryError:扩展栈是无法申请足够内存

本地方法栈

作用

为Native方法提供服务

特性

线程私有

方法区

作用

存储类信息、常量、静态变量、即时编译器编译后的代码,包含运行时常量池,存放编译期生成的字面量、符号引用及运行时添加的常量

特性
  1. 线程共享
  2. 可能抛出OutOfMemoryError

堆是Java应用程序最重要的一个内存结构

作用

存放所有对象实例和数组(通过 new 关键字创建的对象)

特性
  1. 线程共享
  2. 可能抛出OutOfMemoryError
  3. 通过 -Xmx-Xms 设置最大和初始堆大小,最好是设置一致
堆内存划分
  1. 新生代
    • Eden区:对象初次分配的区域
    • Survivor区:存放经过Minor GC后存活的对象
  2. 老年代:存放长期存活的对象(经过多次GC未被回收)
  3. 老年代:新生代=2:1,Eden区:Survivor0区:Survivor1区=8:1:1
相关推荐
苏三说技术7 分钟前
LangChain4j 和 LangGraph4j,哪个更好?
后端
ServBay1 小时前
7 个AI开发中真正用得上的 MCP Server,配合Claude Code食用效果更佳
后端·claude·mcp
妙码生花1 小时前
从 PHP 到 AI + Golang,程序员自救转型手记(十五):优化细节、网络请求封装
前端·后端·ai编程
用户6757049885022 小时前
Go 语言里判断字符串为空,90% 的人都写错了!
后端·go
用户6757049885022 小时前
Go 进阶必修:90% 的人都没用对的“表驱动法”
后端·go
小兔崽子去哪了2 小时前
Java 生成二维码解决方案
java·后端
苍何2 小时前
懂事的 Agent 已经开始自己看屏幕干活了,效率起飞!
后端
掘金码甲哥3 小时前
1分钟买不了吃亏系列: nginx动态域名解析
后端
神奇小汤圆3 小时前
2026大厂Java岗面试记录:八股+场景+项目+AI,一文讲透快速上岸路径(含答案)
后端
神奇小汤圆3 小时前
我说MySQL每张表最好不超过2000万条数据,面试官让我回去等通知?
后端