一、背景:
使用java spring boot ,实现linux 服务器 内存溢出情况。
二、方案
1、打包成war包,可以直接将war包部署在tomcat容器里
2、spring boot,打包成jar包。打的jar包,内置了tomcat,所以在服务器上,直接启jar包就行,没有必要放在tomcat容器里部署,在启动jar包时,可以配置线程池等。
这里用 spring boot,打包成jar。
2.1 、新建 项目
java 选择8

勾选spring web

Spring boot 版本 <version>2.7.14</version>
2.2 编写项目

OOMController.java
package com.example.testoom;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@RestController
public class OOMController {
@GetMapping("/memoryLeak")
public String memoryLeak() {
// 是否调用remove方法
boolean doRemove = false;
// 加锁,让多个线程串行执行,避免多个线程同时占用内存导致的内存溢出问题
final Object lockObj = new Object();
// 开启20个线程
ExecutorService executorService = Executors.newFixedThreadPool(20);
// 为了不重复使用线程,用Map标记一下已经已使用过的线程,
Map<Long, Integer> threadIdMap = new ConcurrentHashMap<>();
// 循环向线程变量中设置数据 1024 * 1024 = 1M
for (int i = 0; i < 20; i++) {
executorService.execute(() -> {
synchronized (lockObj) {
Integer num = threadIdMap.putIfAbsent(Thread.currentThread().getId(), 1);
if (num == null) {
ThreadLocal<Byte[]> threadLocal = new ThreadLocal<>();
threadLocal.set(new Byte[1024 * 1024]);
// 手工回收
System.gc();
try {
// 调用GC后不一定会马上回收
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName());
}
}
);
}
// 线程池中的任务全部执行完毕
executorService.shutdown();
return "ok";
}
}
配置端口号,8083
application.properties
spring.mvc.view.prefix=classpath:/templates/ spring.mvc.view.suffix=.html server.port=8083
2.3 打jar包

(如果之前,大过,要先cleam清理一下)
jar包在target目录下

2.4 部署服务在服务器启动该jar包
1、先将jar包上传到服务器。
2、启动jar包
参数解释 nohup 表示不挂断的运行, 注意并没有后台运行的功能,用 nohup 命令可以使命令永久的执行, 和客户端没有任何关系 & 表示是后台运行 -Xms20M -Xmx20M -Xmn10M -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=./heapdump.hprof 表示为了发生OOM的时候会自动导出Dump文件
java -jar -Xms300M -Xmx300M -Xmn100M testOOM-0.0.1-SNAPSHOT.jar -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=./heapdump.hprof &

3 请求接口
部署之后请求一下接口: http://192.168.1.3:8083/memoryLeak

三、使用jmeter压这个接口


加大线程对接口进行压测,通过jmap或Arthas监控项目运行过程中(cpu、内存、磁盘)等关键信息
四、案例分析
分析思路1:
1、jmap 常用参数:
jmap命令进行堆转储(heap dump)时,如果 Java 进程遇到内存溢出(OutOfMemoryError),jmap命令会自动停止并输出堆转储文件。
jmap -histo:pid:打印指定 Java 进程(pid)的内存使用情况摘要。 jmap heap pid:打印指定 Java 进程(pid)的堆(heap)信息。 jmap -dump:format=b,file=output.dump:生成堆转储快照(dump 文件),并指定文件格式(b 表示二进制输出)。 jmap -finalize:查看 finalize 执行队列,即等待垃圾回收的线程。
获取当前进程id

存储dump堆文件:
jmap -dump:live,format=b,file=heap_jvmpertest_2023081901.hprof 9557

从项目运行日志关键字:java.lang.OutOfMemoryError: Java heap space,当前项目出现了内存泄露的情况

2、使用 jstat -gcutil 线程ID 9557 查看

结果排查, FGC比较频繁,多半是有问题
分析思路2:
Arthas定位分析
java -jar arthas-boot.jar 9557

1、输入 dashboard 查看线程整体的运行情况,heap的消耗情况,以及运行时环境

2、 输入 heapdump命令 将 hprof 文件下载下来,通过mat工具进行分析

mat工具直接加载hprof文件

点击Histogram,列出内存中的对象,对象的个数及大小
发现出问题的对象为 class java.lang.Byte[]

Byte[] 占用了空间的 90%以上,基本可以断定为是 Byte[] 没有被回收导致的内存泄漏
参考笔记:
https://blog.csdn.net/bookssea/article/details/124073742
https://blog.csdn.net/huangliangbao2009/article/details/129785174