java脚本模拟服务器内存溢出实战&服务器部署java项目

发布时间 2023-08-20 21:33:44作者: 橙子全栈测试笔记

一、背景:

使用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