「轻盈」之旅:OOM故障重现与解决

news2024/11/19 15:36:31

前期准备

  1. 本项目均采用 VisualVM 2.1.10 进行dump文件的分析。JDK1.8及之前所在目录的bin目录下有自带的VisualVM,JDK1.8以后需要自行手动安装下载。
    下载地址:https://visualvm.github.io/download.html
    IDEA插件配置:在Plugins里搜索visualVM Launcher即可。(也可以不用配置,直接下载客户端软件)后续只要在配置下载安装好的VisualVM程序地址即可,这样就能直接在IDEA中根据指定的类启动VisualVM了,不需要在独立的VisualVM里找指定路径装配。
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

  2. 项目采用JDK1.8对OOM溢出进行分析。在进行模拟时,需要配置一些参数,例如:

注意:这里可以通过jmap指定打印他的内存快照dump文件,不过有的情况打印不了,我们会设置vm参数让程序自动生成dump文件。

-XX:+PrintGCDetails (让 JVM 在执行垃圾收集时输出详细的日志信息)
-XX:MetaspaceSize=64m 
-XX:+HeapDumpOnOutOfMemoryError (打印出现OOM的异常信息dump文件)
-XX:HeapDumpPath=heap/heapdump3.hprof (打印的文件的具体位置)
-XX:SurvivorRatio=8 (设置年轻代中 Survivor 空间的比例)
-XX:+PrintGCDateStamps (使得 GC 日志中的每一条记录都会带上时间戳)
-Xms50M -Xmx50M 
-Xloggc:log/gc-oomHeap.log  (打印的GC日志文件的路径)

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

  1. 分析GC和dump文件:一般我们在命令中会设置JVM参数,执行启动类的时候会自动生成gc日志和dump文件。gc日志我们可以在GC Easy(https://gceasy.io/gc-dashboard.jsp)中分析即可。
    在这里插入图片描述
    接下来我们就要在VisualVM中装入我们要分析的dump文件即可。
    在这里插入图片描述
    在这里插入图片描述

案例一二三代码链接

https://pan.baidu.com/s/1C8IMG4ZXrqjQdYb4B-Z5gg 提取码: syhh


OOM案例一:堆溢出(Java heap space)

案例模拟

这是一个简单的SpringBoot项目,DemoApplication类是整个当前模块项目的启动类,端口号为8848,如果端口号被占用,可以杀掉当前占用的进程(kill -9 PID),或者在application-dev.yml中修改端口号:

在这里插入图片描述

我们模拟发送请求:http://localhost:8848/add

在这里插入图片描述

JVM参数配置

-XX:+PrintGCDetails 
-XX:MetaspaceSize=64m 
-XX:+HeapDumpOnOutOfMemoryError 
-XX:HeapDumpPath=heap/heapdump3.hprof 
-XX:SurvivorRatio=8 
-XX:+PrintGCDateStamps 
-Xms50M -Xmx50M 
-Xloggc:log/gc-oomHeap.log

运行结果

在这里插入图片描述

gc文件分析

GC Easy(https://gceasy.io/gc-dashboard.jsp)中分析即可:
通过下图可以看出,我们的程序进行了大量的Full GC操作,导致内存溢出了。

在这里插入图片描述

dump文件分析

首先我们在Summary中找到出现异常的线程,红色标记:
在这里插入图片描述

这时我们可以点击查看view all

在这里插入图片描述
找到出现异常线程的线程报告后,再找到我们代码中出现错误的行号(这点很重要)。另外,还可以通过对象分配来查看,是否存在大对象:

在这里插入图片描述
图片中标记为红色的区域显示了每个类的对象数量。例如,“com.atguigu.demo.bean.People”类有1,215,488个实例,这是所有对象中最多的。另一个红色的区域显示了每个类占用的内存大小。例如,“com.atguigu.demo.bean.People”类占用了大约39MB的内存空间。这表明该类可能存在内存泄漏或者过度分配的情况。

回到代码,果然:

在这里插入图片描述

原因及解决方法

原因
1、代码中可能存在大对象分配。
2、可能存在内存泄漏,导致在多次GC之后,还是无法找到一块足够大的内存容纳当前对象。

解决方法
1、检查是否存在大对象的分配,最有可能的是大数组分配。
2、如果没有找到明显的内存泄漏,使用 -Xmx 加大堆内存。
3、还有一点容易被忽略,检査是否有大量的自定义的 Finalizable 对象,也有可能是框架内部提供的,考虑其存在的必要性。

OOM案例二:元空间溢出(Metaspace)

案例模拟

这段代码使用了 CGLIB(Code Generation Library)框架中的 Enhancer 类来动态创建 People 类的子类实例。

在这里插入图片描述

JVM参数配置

-XX:+PrintGCDetails 
-XX:MetaspaceSize=60m 
-XX:MaxMetaspaceSize=60m 
-Xss640K 
-XX:+HeapDumpOnOutOfMemoryError 
-XX:HeapDumpPath=heap/heapdumpMeta.hprof 
-XX:SurvivorRatio=8 
-XX:+TraceClassLoading 
-XX:+TraceClassUnloading 
-XX:+PrintGCDateStamps 
-Xms60M -Xmx60M 
-Xloggc:log/gc-oomMeta.log

运行结果

当我们正常启动程序时,使用VisualVM查看对应的类的元空间(Metaspace)。此时一切正常,最大元空间我们设置为60M,此时只使用了30M左右。

在这里插入图片描述

模拟一下,访问http://localhost:8848/metaSpaceOom,访问前记得先clear下控制台输出。

当我们访问http://localhost:8848/metaSpaceOom这个页面时,发现出现了OOM异常了。

在这里插入图片描述

gc文件分析

在这里插入图片描述

除了可以使用GC Easy这个工具外呢,我们还可以使用控制台的jps命令,列出正在运行的 JVM 进程的状态信息,包括进程 ID 和主类名称。然后使用jstat命令统计对应进程ID的垃圾收集状况,每1秒钟执行一次。

在这里插入图片描述
可以看到元空间已使用的空间(MU)非常接近于上限了(MC),而且出现了大量的Full GC(829次)。
在这里插入图片描述

如果直接观察生成的gc日志,我们也不难看出:后期出现了大篇幅的Full GC。

在这里插入图片描述

dump文件分析

回到VisualVM,我们明显可以看到元空间占用和CPU明显飙升了,出现了满负载的情况,元空间也达到了我们所预设的最大上限。

在这里插入图片描述

老样子,在Summary中找出现OOM的线程,定位到具体的代码行号。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

原因及解决方法

JDK8后,元空间替换了永久代,元空间使用的是本地内存。

原因:
1.运行期间生成了大量的代理类,导致方法区被撑爆,无法卸载
2.应用长时间运行,没有重启
3.元空间内存设置过小

解决方法:
因为该 OOM 原因比较简单,解决方法有如下几种:
1.检查是否永久代空间或者元空间设置的过小
2.检查代码中是否存在大量的反射操作
3.dump之后通过VisualVM检査是否存在大量由于反射生成的代理类

setUseCache(true) 时,CGLIB 会尝试重用已存在的类定义,从而减少了类定义的数量,避免了由于频繁创建类而导致的内存问题。因此,设置为 true 可以有效防止因类定义过多而导致的内存溢出。

在这里插入图片描述

OOM案例三:GC overhead limit exceeded

案例模拟

注意模拟的时候,一定要分开执行,每次调整JVM的日志和dump输出的文件名,避免冲突!!!

Java源代码:

package com.atguigu.oom;

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

/**
 * 案例3:测试 GC overhead limit exceeded
 * @author shkstart
 * @create 16:57
 */
public class OOMTest {
    public static void main(String[] args) {
    	// 出现GC overhead limit exceeded异常
        test1();
        
		// 出现Java heap space异常
        test2();
    }

    public static void test1() {
        int i = 0;
        List<String> list = new ArrayList<>();
        try {
            while (true) {
                list.add(UUID.randomUUID().toString().intern());
                i++;
            }
        } catch (Throwable e) {
            System.out.println("************i: " + i);
            e.printStackTrace();
            throw e;
        }
    }

    public static void test2() {
        String str = "";
        Integer i = 1;
        try {
            while (true) {
                i++;
                str += UUID.randomUUID();
            }
        } catch (Throwable e) {
            System.out.println("************i: " + i);
            e.printStackTrace();
            throw e;
        }
    }

}

JVM参数配置

执行test1():

-XX:+PrintGCDetails
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=heap/dumpExceeded.hprof
-XX:+PrintGCDateStamps
-Xms5M
-Xmx5M
-Xloggc:log/gc-oomExceeded.log

执行test2():

-XX:+PrintGCDetails
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=heap/dumpExceeded1.hprof
-XX:+PrintGCDateStamps
-Xms5M
-Xmx5M
-Xloggc:log/gc-oomExceeded1.log

运行结果

执行test1():
在这里插入图片描述

执行test2():

在这里插入图片描述

gc文件分析

在这里插入图片描述
通过查看GC日志可以发现,系统在频繁性的做FULL GC,但是却没有回收掉多少空间,那么引起的原因可能是因为内存不足,也可能是存在内存泄漏的情况,接下来我们要根据堆dump文件来具体分析。

dump文件分析

test1():
在这里插入图片描述

在这里插入图片描述

test2():

在这里插入图片描述

在这里插入图片描述

原因及解决方法

根据业务来修改是否需要死循环。

原因:
这个是JDK6新加的错误类型,一般都是堆太小导致的。Sun 官方对此的定义:超过98%的时间用来做GC并且回收了不到2%的堆内存时会抛出此异常。 本质是一个预判性的异常,抛出该异常时系统没有真正的内存溢出。

第一段代码: 运行期间将内容放入常量池的典型案例intern()方法。

  • 如果字符串常量池里面已经包含了等于字符串X的字符串,那么就返回常量池中这个字符串的引用;
  • 如果常量池中不存在,那么就会把当前字符串添加到常量池并返回这个字符串的引用

第二段代码: 不停的追加字符串str
你可能会疑惑,看似demo也没有差太多,为什么第二个没有报GCoverhead limit exceeded呢?以上两个demo的区别在于:

  • Java heap space的demo每次都能回收大部分的对象(中间产生的UUID),只不过有一个对象是无法回收的,慢慢长大,直到内存溢出。
    在这里插入图片描述

  • GC overhead limit exceeded的demo由于每个字符串都在被list引用,所以无法回收,很快就用完内存,触发不断回收的机制。
    在这里插入图片描述

解决方法:
1.检查项目中是否有大量的死循环或有使用大内存的代码,优化代码
2.添加参数-XX:-UseGcoverheadLimit禁用这个检查,其实这个参数解决不了内存问题,只是把错误的信息延后,最终出现 java.lang.OutOfMemoryError: Java heap space.
3. dump内存,检查是否存在内存泄漏,如果没有,加大内存。

OOM案例四:线程溢出(unable to create new native thread)

(一定不要在本地执行,当系统资源耗尽的时候,电脑直接挂机重启了)

案例模拟

在下面这个例子中,我们创建了一个无限循环,不断地创建并启动新线程:

package com.atguigu.oom;

import java.util.concurrent.CountDownLatch;

/**
 * 案例4:线程溢出
 * @author shkstart
 * @create 17:45
 */
public class TestNativeOutOfMemoryError {
    public static void main(String[] args) {
        for (int i = 0; ; i++) {
            System.out.println("i = " + i);
            new Thread(new HoldThread()).start();
        }
    }
}

class HoldThread extends Thread {
    CountDownLatch cdl = new CountDownLatch(1);

    @Override
    public void run() {
        try {
            cdl.await();
        } catch (InterruptedException e) {
        }
    }
}

注意:在真实环境中,你应该谨慎使用这样的代码,因为它可能会耗尽系统资源。在生产环境中,你需要检查线程的生命周期管理,确保及时释放不再使用的线程资源。同时,你也应该关注系统的线程限制和线程栈大小设置,以确保它们满足应用程序的需求。

运行结果

如果你的机器上的可用线程数量达到系统的限制,或者剩余的线程栈空间不足以创建新的线程,就会抛出 java.lang.OutOfMemoryError: unable to create new native thread 异常。

原因及解决方法

出现这种异常大多都是创建了大量的线程导致的。

解决方向1:

  • 通过 -Xss 设置每个线程栈大小的容量。
  • JDK5.0以后每个线程堆栈大小为1M,以前每个线程堆栈大小为256K。
  • 正常情况下,在相同物理内存下,减小这个值能生成更多的线程。但是操作系统对一个进程内的线程数还是有限制的,不能无限生成,经验值在3000~5000左右。
  • 能创建的线程数的具体计算公式如下:
    (MaxProcessMemory -JVMMemory - ReservedOsMemory) / (ThreadStackSize)= Number of threads

MaxProcessMemory 指的是进程可寻址的最大空间
JVMMemory:JVM内存
ReservedOsMemory:保留的操作系统内存
ThreadStackSize:线程栈的大小

  • 当你创建一个线程的时候,虚拟机会在JVM内存创建一个Thread对象同时创建一个操作系统线程,而这个系统线程的内存用的不是JVMMemory,而是系统中剩下的内存(MaxProcessMemory -JVMMemory - ReservedOsMemory).
  • 由公式得出结论:你给JVM内存越多,那么你能创建的线程越少,越容易发生java.lang.OutOfMemoryError: unable to create new native thread

问题解决:

  1. 如果程序中有bug,导致创建大量不需要的线程或者线程没有及时回收,那么必须解决这个bug,修改参数是不能解决问题的。
  2. 如果程序确实需要大量的线程,现有的设置不能达到要求,那么可以通过修改MaxProcessMemory,JVMMemory,ThreadstackSize这三个因素,来增加能创建的线程数。
  3. MaxProcessMemory 使用64位操作系统
  4. JVMMemory 减少JVMMemory的分配
  5. ThreadStackSize 减小单个线程的栈大小

解决方向2:

线程总数也受到系统空闲内存和操作系统的限制,检查是否该系统下有此限制:

  • /proc/sys/kernel/pid_max 系统最大PID值,在大型系统里可适当调大
  • /proc/sys/kernel/threads-max 系统允许的最大线程数
  • maxuserprocess(ulimit -u) 系统限制某用户下最多可以运行多少进程或线程
  • /proc/sys/vm/max_map_count

max_map_count文件包含限制一个进程可以拥有的VMA(虚拟内存区域)的数量。虚拟内存区域是一个连续的虚拟地址空间区域。在进程的生命周期中,每当程序尝试在内存中映射文件,链接到共享内存段,或者分配堆空间的时候,这些区域将被创建。调优这个值将限制进程可拥有VMA的数量。限制一个进程拥有VMA的总数可能导致应用程序出错,因为当进程达到了VMA上限但又只能释放少量的内存给其他的内核进程使用时,操作系统会抛出内存不足的错误。如果你的操作系统在NORMAL区域仅占用少量的内存,那么调低这个值可以帮助释放内存给内核用。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2183769.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

2-109 基于matlab-GUI的BP神经网络

基于matlab-GUI的BP神经网络&#xff0c;10种不同分布的数据样本&#xff0c;9种不同的激活函数&#xff0c;可更改升级网络结构参数&#xff0c;对比各种方法参数下的训练测试效果&#xff0c;实时显示预测过程。程序已调通&#xff0c;可直接运行。 下载源程序请点链接&…

【简介Sentinel-1】

Sentinel-1是欧洲航天局哥白尼计划&#xff08;GMES&#xff09;中的地球观测卫星&#xff0c;由Sentinel-1A和Sentinel-1B两颗卫星组成。以下是对Sentinel-1的详细介绍&#xff1a; 一、基本信息 卫星名称&#xff1a;Sentinel-1 所属计划&#xff1a;欧洲航天局哥白尼计划…

【CSS】兼容处理

兼容前缀兼容查询 由于不同浏览器对CSS标准的支持程度不同&#xff0c;可能会导致在不同浏览器中出现样式差异。为了解决这个问题&#xff0c;需要采取一些措施来提高CSS的兼容性 兼容前缀 兼容前缀针对的浏览器-webkit-WebKit 内核浏览器&#xff0c;如&#xff1a;Safari 、…

.NET Core 集成 MiniProfiler性能分析工具

前言&#xff1a; 在日常开发中&#xff0c;应用程序的性能是我们需要关注的一个重点问题。当然我们有很多工具来分析程序性能&#xff1a;如&#xff1a;Zipkin等&#xff1b;但这些过于复杂&#xff0c;需要单独搭建。 MiniProfiler就是一款简单&#xff0c;但功能强大的应用…

进击J9:Inception v3算法实战与解析

&#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊 一、实验目的&#xff1a; 了解并学习InceptionV3相对于InceptionV1改进了哪些地方&#xff08;重点&#xff09;使用Inception v3完成天气识别案例 二、实验环…

Android 12.0 关于定制自适应AdaptiveIconDrawable类型的动态时钟图标的功能实现系列一

1.前言 在12.0的系统rom定制化开发中,在关于定制动态时钟图标中,原系统是不支持动态时钟图标的功能,所以就需要从新 定制动态时钟图标关于自适应AdaptiveIconDrawable类型的样式,就是可以支持当改变系统图标样式变化时,动态时钟 图标的背景图形也跟着改变,所以接下来就来…

OpenFeign微服务部署

一.开启nacos 和redis 1.查看nacos和redis是否启动 docker ps2.查看是否安装nacos和redis docker ps -a3.启动nacos和redis docker start nacos docker start redis-6379 docker ps 二.使用SpringSession共享例子 这里的两个例子在我的一个博客有创建过程&#xff0c…

通信工程学习:什么是LTE长期演进

LTE:长期演进 LTE(Long Term Evolution,长期演进)是由3GPP(The 3rd Generation Partnership Project,第三代合作伙伴计划)组织制定的UMTS(Universal Mobile Telecommunications System,通用移动通信系统)技术标准的长期演进。以下是对LTE的详细解释: 一、定…

音乐制作软件FL Studio 24.1.1.4285 中文完整版新功能介绍及如何安装激活FL Studio 24

FL Studio 24.1.1.4285 中文完整版又被国内网友称之为水果音乐制作软件24&#xff0c;是Image-Line公司成立26周年而发布的一个版本&#xff0c;是目前互联网上最优秀的完整的软件音乐制作环境或数字音频工作站&#xff0c;包含了编排&#xff0c;录制&#xff0c;编辑&#xf…

笔墨歌盛世 丹青绘匠心,艺术赋能“百千万工程”

9月30日上午&#xff0c;乡村有“艺”思——2024 年三乡镇乡村文化艺术周启动仪式暨“崛起的力量”余镇河深中通道主题美术作品展开幕仪式在中山市三乡镇古鹤村成荣美术馆举行。 中山市文联党组成员、专职副主席卢曙光&#xff0c;三乡镇党委委员艾立强&#xff0c;中山市文化馆…

leetcode每日一题day21(24.10.1)——最低票价

看到题目&#xff0c;最低消费又有各种的方案&#xff0c;与结合往期每日一题很就没出动态规划&#xff0c;就感觉这题很像动态规划。 思路:对于第X天&#xff0c;买票有三种方案&#xff0c;即从&#xff0c;X-1天买一天的票&#xff0c;X-7买7天的票&#xff0c;X-30买三十天…

iSTFT 完美重构的条件详解

目录 引言1. 短时傅里叶变换&#xff08;STFT&#xff09;与逆变换&#xff08;iSTFT&#xff09;概述2. 完美重构的条件3. 数学推导4. 实现要点5. 示例代码6. 总结 引言 在数字信号处理领域&#xff0c;短时傅里叶变换&#xff08;Short-Time Fourier Transform&#xff0c;简…

Java Web开发详解:从入门到实践

目录 引言 Java Web开发的优势 Java Web开发核心概念 Servlet和JSP Servlet JSP&#xff08;JavaServer Pages&#xff09; MVC架构 JDBC和数据库访问 JDBC概述 数据库连接示例 常用的Java Web框架 Spring MVC Hibernate MyBatis 对比常用框架 Java Web开发流程…

YOLOv5改进系列(1)——添加CBAM注意力机制

一、如何理解注意力机制 假设你正在阅读一本书&#xff0c;同时有人在你旁边说话。当你听到某些关键字时&#xff0c;比如“你的名字”或者“你感兴趣的话题”&#xff0c;你会自动把注意力从书上转移到他们的谈话上&#xff0c;尽管你并没有完全忽略书本的内容。这就是注意力机…

docker零基础入门教程

注意 本系列文章已升级、转移至我的自建站点中&#xff0c;本章原文为&#xff1a;Docker入门 目录 注意1.前言2.docker安装3.docker基本使用4.打包docker镜像5.docker进阶 1.前言 如果你长期写C/C代码&#xff0c;那你应该很容易发现C/C开源项目存在的一个严重问题&#xff…

Xshell-8下载安装教程

下载地址 https://www.xshell.com/zh/free-for-home-school/ 新建Xshell文件夹 点击安装程序 选择新建Xshell文件夹 默认即可 点击安装 注册 提交后点击邮箱收到的链接 点击确认 安装完成

【C/C++】错题记录(三)

题目一 题目二 题目三 题目四 题目五 题目六 题目七&#xff1f;&#xff1f;&#xff1f; 题目八 这道题主要考查对数据类型和位运算的理解与运用。 分析选项 A&#xff1a; *((unsigned char *)(&number) 1)0xcd; 这里将 number 的地址强制转换为 unsigned char* 类型&a…

Qt界面优化——QSS

文章目录 QSS基本语法使用示例样式和代码分离选择器用法子控件选择器伪类选择器盒子模型控件样式示例按钮复选框输入框列表框菜单 登录界面 QSS基本语法 Qt对界面进行优化&#xff0c;主要采用的是QSS。 选择器 {属性名: 属性值; }选择器&#xff1a;先选择某个/类控件&#…

【JVM】双亲委派机制打破双亲委派机制

双亲委派机制 类加载器的双亲委派机制 由于Java虚拟机中有多个类加载器&#xff0c;双亲委派机制的核心是解决一个类到底由谁加载的问题。 双亲委派的作用&#xff1a; 保证类加载的安全性&#xff1a;通过双亲委派机制避免恶意代码替换 JDK中的核心类库。避免重复加载&…

算法题总结(五)——普通数组

普通数组 #238、除自身以外数组的乘积 给你一个整数数组 nums&#xff0c;返回 数组 answer &#xff0c;其中 answer[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积 。 题目数据 保证 数组 nums之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内。 请 不要…