环境准备
- centos 7
- tomcat 8 / 7
- jdk1.8
- 开发工具idea
- spring boot2.0+
- MemoryAnalyzer
1 idea创建spring boot默认项目
贴下pom.xml文件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.0.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.learning</groupId>
<artifactId>jvm-learn</artifactId>
<version>1.0</version>
<name>jvm-learn</name>
<packaging>war</packaging>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
简单说下这个listing(商品) 模拟一个大的实体对象(详见下)
这里我们采用war包的形式发布 spring boot打包war 使用tomcat 部署项目 所以需要集成SpringBootServletInitializer
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
@SpringBootApplication
public class JvmLearnApplication extends SpringBootServletInitializer {
public static void main(String[] args) {
SpringApplication.run(JvmLearnApplication.class, args);
}
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
return builder.sources(JvmLearnApplication.class);
}
}
代码基础阶段准备结束
2 准备模拟问题代码
package com.learning.controller;
import com.learning.dto.Listing;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
@RestController
@RequestMapping("/jvm")
@Slf4j
public class JvmController {
private static List<Listing> lists = new ArrayList<>();
@GetMapping("/mem")
public String menTest() {
for (int i = 0; i < 20; i++) {
Listing listing = new Listing();
listing.setCode(i + "code");
listing.setName(i + "name");
Byte[] bytes = new Byte[1024 * 1024 * 10];
listing.setMem(bytes);
lists.add(listing);
}
return "success";
}
}
在简单解释下 把lists 放到属性位置上是为了让该对象可达(GC root如果没有指针指向它,lists这个对象就会被回收。看不到效果) 然后就是打包发布到tomcat
- 打包项目
- 将war包发布到tomcat 的root目录下面 使用命令解压文件
unzip jvm-learn-1.0.war
- 启动tomcat (切换到bin目录下面 )使用命令启动服务
sh startup.sh
3 分析问题
- 访问接口 http://192.168.80.128:8080/jvm/mem (访问结果可能报错 500 因为已经内存泄露了)
- 使用命令
top
查看服务器内存 ,然后按大写M
以内存占用倒叙排序
- 使用jdk提供命令
jmap -dump:format=b,file=heapdump.phrof 3687
dump下内存使用情况 (会在当前文件夹下生成phrof文件)
- 分析内存
使用eclipse 工具MAT 分析内存文件 MAT绿色版下载地址 。并且选择导入刚才使用jmap生成的文件
导入文件后 注意观察下面几个地方
点击Histogram
以Retain heap 排序 Retain heap 简单解释下 Retain heap 是指这个对象中所指向的对象占用内存的大小 问题基本找到
查看这个listing 到底那里用到 里面存放了什么东西导致内存溢出
按照上面操作后 会显示出来
大体上问题已经定位到. 就是因为 JvmController 的 menTest() 方法处理不合适导致 lists 里面存放了大对象 并且没有被GC回收然后就是jvm内存溢出。