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
相关推荐
实名上网宋凯宣17 小时前
Django
后端·python·django
利威尔xu19 小时前
Go IM 源码实战(14):本地缓存快 3000 倍,失效却难 10 倍
后端·缓存·微服务·面试·golang
逃逸线LOF19 小时前
Ribbon负载均衡原理(Irule组件、源码分析)
后端·spring cloud·ribbon
慢云智慧空间20 小时前
从设备智能到空间理解,空间智能终端解决方案商如何重新定义行业标准
后端
利威尔xu20 小时前
Go IM 源码实战(13):30 个会话只有 2 个红点,未读数错在三条路径
redis·后端·微服务·面试·golang
薛定谔的猫-菜鸟程序员20 小时前
软件架构设计师开发资料大全(从入门到精通)
后端·python·系统架构·flask·架构设计师
IT_陈寒1 天前
SpringBoot多环境配置把我坑惨了,这个踩坑经验你最好看看
前端·人工智能·后端
码事漫谈1 天前
DeepSeek 搞了个 "Super-agent",但不是超级智能
后端
吴凡wudianmei1 天前
从 0 搭一个 AI Agent(四):SSE 跑通之后,三处“契约错配”与它们的修复边界
前端·后端·aigc
小兔子1 天前
defer 的参数在写下那一刻就求值了,循环里这样写会踩坑
后端