一、创建springBoot项目

新建的项目需引入web依赖支持,方便我们通过http调用
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.5.2</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.javapc</groupId>
    <artifactId>demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>javapc</name>
    <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</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <!-- 引入web依赖 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <!-- springboot打包插件 -->
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <mainClass>com.javapc.demo.JavapcApplication</mainClass>
                    <!-- <skip>true</skip> -->
                </configuration>
                <executions>
                    <execution>
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

</project>

application.yml文件如下:

server:
  port: 8080
  servlet:
    context-path: /

新建一个方法:

package com.javapc.demo.test;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.ArrayList;
import java.util.List;

@RestController
public class TestController {

	List<Student> list = new ArrayList<>();

	@GetMapping("/normal")
	public String normal(){
		System.out.println("张三是个法外狂徒!!!");

		return "访问成功,并返回:张三是个法外狂徒!!!";
	}

	@GetMapping("/except")
	public String exception(){
		for(int i = 1;;i ++){
			Student student = new Student();
			student.setName("同一个人");
			student.setGender("不分男女");
			System.out.println("当前循环次数:" + i);
			list.add(student);
		}
	}
}

二、使用jvisualvm监控

1. 配置springboot项目,修改jvm参数,使其发生内存溢出错误(java.lang.OutOfMemoryError)

内存溢出在开发中和线上环境发生的概率很高,会造成系统运行缓慢,或者直接宕机的严重后果。
为方便测试,模拟一下内存溢出的情况,对问题进行排查,线上出问题需要生成一个快照(hprof文件),通过jvisualvm进行heapdump分析。

-Xms100m -Xmx200m -Xmn100m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/Users/zhishengjie/workspace/learning/vm/jvmtest.hprof

在这里插入图片描述
参数说明:
-Xms为jvm启动时分配的内存,比如-Xms200m,表示分配200M。
-Xmx 为jvm运行过程中分配的最大内存,比如-Xmx100m,表示jvm进程最多只能够占用100M内存。
-Xmn设置年轻代大小为100m。整个JVM内存大小=年轻代大小 + 年老代大小 + 持久代大小。持久代一般固定大小为64m,所以增大年轻代后,将会减小年老代大小。此值对系统性能影响较大,Sun官方推荐配置为整个堆的3/8。
-XX:+HeapDumpOnOutOfMemoryError 设置当首次遭遇内存溢出时导出此时堆中相关信息。
-XX:HeapDumpPath 定导出堆信息时的路径或文件名(这里可以设置文件名字,也可以不设置),不设置名字它会自己生成的。
-Xss设置每个线程的堆栈大小。JDK5.0以后每个线程堆栈大小为1M,以前每个线程堆栈大小为256K。更具应用的线程所需内存大小进行调整。在相同物理内存下,减小这个值能生成更多的线程。但是操作系统对一个进程内的线程数还是有限制的,不能无限生成,经验值在3000~5000左右。
-XX:MaxPermSize=128m 设置永久代的大小,此属性在jdk8之后不再提供。
注:如果1.7之前为-XX:PermSize=64m -XX:MaxPermSize=128m ,1.8需要变成
-XX:MetaspaceSize=64m -XX:MaxMetaspaceSize=128m 否则起不来。
-XX:+UseConcMarkSweepGC 设置年老代为并发收集。测试配置这个参数以后,参数-XX:NewRatio=4就失效了,所以,此时年轻代大小最好用-Xmn设置,因此这个参数不建议使用。
-XX:CMSFullGCsBeforeCompaction=5 由于并发收集器不对内存空间进行压缩、整理,所以运行一段时间以后会产生“碎片”,使得运行效率降低。此参数设置运行次FullGC以后对内存空间进行压缩、整理。
-XX:+UseCMSCompactAtFullCollection 打开对年老代的压缩。可能会影响性能,但是可以消除内存碎片。
-XX:+PrintGC 每次GC时打印相关信息。
-XX:+PrintGCDetails 每次GC时打印详细信息。
-XX:+PrintGCTimeStamps 打印每次GC的时间戳。
-Xloggc:/tmp/jvm.log 设置垃圾回收日志打印的文件,文件名称可以自定义。

2. 打开jvisualvm

环境mac

终端输入

/usr/libexec/java_home -V

在这里插入图片描述
Java安装路径就是/Library/Java/JavaVirtualMachines/jdk1.8.0_261.jdk/Contents/Home

进入bin目录,启动jvisualvm

zhishengjie@zhishengjiedeMacBook-Pro bin % /usr/libexec/java_home -V
Matching Java Virtual Machines (2):
    1.8.261.12 (x86_64) "Oracle Corporation" - "Java" /Library/Internet Plug-Ins/JavaAppletPlugin.plugin/Contents/Home
    1.8.0_261 (x86_64) "Oracle Corporation" - "Java SE 8" /Library/Java/JavaVirtualMachines/jdk1.8.0_261.jdk/Contents/Home
/Library/Internet Plug-Ins/JavaAppletPlugin.plugin/Contents/Home
zhishengjie@zhishengjiedeMacBook-Pro bin % ./jvisualvm 
2021-07-20 17:01:56.233 java[10345:473811] TSM AdjustCapsLockLEDForKeyTransitionHandling - _ISSetPhysicalKeyboardCapsLockLED Inhibit

在这里插入图片描述

3. jvisualvm安装visual GC

在这里插入图片描述
在这里插入图片描述
安装之后会显示visual Gc导航栏如下图:
在这里插入图片描述

3. 使用visual GC查看堆栈情况

启动springboot项目,使用浏览器访问http://localhost:8080/except
在这里插入图片描述
在这里插入图片描述
我们可以通过visual GC观察堆内存情况。
通过代码我们可知,由于新建的student对象都被list对象强引用,使得gc也无法将其收集,所以当老年代被装满时就是发生java.lang.OutOfMemoryError。

扩展:visual GC说明 compile time JIT编译时间 图上标题明说: 3035 次编译,使用840.942秒 class
Loader Time 类加载时间 图上标题明说:6211 个已经加载 82 未加载 ,使用 1.641秒 GC Time gc 时间
,图上标题说明:43 次执行gc ,共使用了 13.781秒 。最近一次gc的原因 Eden Space 年青代内存说明 最大 99 M
,实际分配内存:34M ,当前使用了 26.345 M , 20 次 的GC 共使用了 559.951s 秒 Surivor
存活区内存说明 最大内存 33M 实际分配 33M 当前使用了 0M old gen 老年代说明 最大内存100 实际分配了 100M
,当前使用 99.876M , 23 次的GC ,共使用了13.221 秒 metaspace 元空间 最大内存 1.027G 实际分配了
31.625M ,当前使用了29.038M

三、使用jvisualvm对堆栈快照进行分析

1. 导入.hprof文件

在这里插入图片描述
在这里插入图片描述

2. 分析

  1. 点击错误线程
    在这里插入图片描述

  2. 找到我们创建的类。
    在这里插入图片描述
    根据异常信息提示,显示是TestController.java:28代码出现的问题。
    在这里插入图片描述
    根据代码可知也许是程序一直往list中添加对象导致的错误。我们可以看一下list的数量,和list中具体的内容。鼠标左键双击elementData

  3. 查看elementData,鼠标左键双击elementData。
    在这里插入图片描述

  4. 可以看到student对象创建了4,102,266个,
    在这里插入图片描述

  5. 也可以点击查看。
    在这里插入图片描述到此可以断定是因为list持有的对象太多导致堆区内存被占满,导致的内存溢出的错误。

扩展: VisualVM提供了一下可视化视图来浏览heap dumps: Summary View(概述) 打开一个heap
dump时,VisualVM默认显示“概述”标签页。概述视图显示了该heap dump的捕获环境和其他系统属性。 概述实体 Classes
View(类) 类视图显示了类列表和其对应的实例数量、所占比例。右击类名选择“Show in Instances
View(在实例视图中显示)”即可查看指定类的实例列表。 点击列头可按指定列排序。可以使用类列表下方的“类名过滤器(Class Name
Filter)”来过滤类,也可以通过右击一个类名选择“Show Only
Subclasses(只显示子类)”来将显示结果限制为指定类的子类。 类视图 Instances View(实例数)
实例数视图显示了选中类的对象实例。在“实例(Instance)”栏中选中一个实例,右侧的“字段(Fields)”和“引用(References)”栏将显示该实例对应的字段和对它的引用。在引用栏中,右击一条并选择“显示最近的垃圾回收根节点(Show
Nearest GC Root)”就会显示最近的垃圾回收根节点。

四、当某个线程发生内存溢出时,其他线程是否能正常执行。

答:可以,当发生内存溢出后我们可以访问http://localhost:8080/normal,是可以正常返回结果的。

五、附件📎

https://download.csdn.net/download/qq_39774931/20387727

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐