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

前期准备

  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.mfbz.cn/a/885810.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

CSS文本格式化

通过 CSS 中的文本属性您可以像操作 Word 文档那样定义网页中文本的字符间距、对齐方式、缩进等等&#xff0c;CSS 中常用的文本属性如下所示&#xff1a; text-align&#xff1a;设置文本的水平对齐方式&#xff1b;text-decoration&#xff1a;设置文本的装饰&#xff1b;te…

Vue项目开发注意事项

事项一&#xff1a;项目代码放在本地怎么运行起来 1、首先确定项目对应的node和npm版本 node下载地址 Index of /dist/https://nodejs.org/dist/ node 与 npm版本对应关系 Node.js — Node.js Releases 2、node卸载的时候&#xff0c;会自动把对应的npm卸载掉 情况1&…

光控资本:股票后边带个u是啥意思,常见股票后缀字母还有哪些?

股票后面带有字母U标明该股票发行人到目前为止还没有盈利&#xff0c;这是根据上交所发布的《关于科创板股票及存托凭证生意相关事项的奉告》中的规则&#xff0c;在上市后实现初度盈利&#xff0c;这个标识就会消除掉。一般是在科创板上市的股票会有U的标明&#xff0c;且一般…

河南做网站与SEO:如何提升搜索引擎排名

河南做网站与SEO&#xff1a;如何提升搜索引擎排名 在当今数字化时代&#xff0c;越来越多的企业意识到互联网的重要性&#xff0c;特别是在河南这样一个快速发展的地区&#xff0c;建立一个优秀的网站已经成为企业发展的必要条件。而在建立网站的同时&#xff0c;SEO&#xff…

【算法】链表:206.反转链表(easy)

系列专栏 《分治》 《模拟》 《Linux》 目录 1、题目链接 2、题目介绍 3、解法&#xff08;快慢指针&#xff09; 解题步骤&#xff1a; 关键点&#xff1a; 复杂度分析&#xff1a; 4、代码 1、题目链接 206. 反转链表 - 力扣&#xff08;LeetCode&#xff09; …

Flutter中使用FFI的方式链接C/C++的so库(harmonyos)

Flutter中使用FFI的方式链接C/C库&#xff08;harmonyos&#xff09; FFI plugin创建和so的配置FFI插件对so库的使用 FFI plugin创建和so的配置 首先我们可以根据下面的链接生成FFI plugin插件&#xff1a;开发FFI plugin插件 然后在主项目中pubspec.yaml 添加插件的依赖路径&…

滑动窗口->dd爱框框

1.题目&#xff1a; 2.题解&#xff1a; 2.1为什么用滑动窗口优化&#xff1a; 因为元素都是大于0的 所以&#xff1a;当找到大于等于x的值时&#xff0c;right可以不用返回 两个指针都往后走&#xff1b;因此可以使用滑动窗口优化暴力解法 2.2&#xff1a;滑动窗口具体使用步…

在掌控板中加载人教版信息科技教学指南中的educore库

掌控板中加载educore库 人教信息科技数字资源平台&#xff08;https://ebook.mypep.cn/free&#xff09;中的《信息科技教学指南硬件编程代码说明》文件中提到“本程序说明主要供教学参考。需要可编程主控板须支持运行MicroPython 脚本程序。希望有更多的主控板在固件中支持ed…

【PyTorch】图像分割

图像分割是什么 Image Segmentation 将图像每一个像素分类 图像分割分类 超像素分割&#xff1a;少量超像素代替大量像素&#xff0c;常用于图像预处理语义分割&#xff1a;逐像素分类&#xff0c;无法区分个体实例分割&#xff1a;对个体目标进行分割全景分割&#xff1a;…

2.点位管理|前后端如何交互——帝可得后台管理系统

目录 前言点位管理菜单模块1.需求说明2.库表设计3.生成基础代码0 .使用若依代码生成器最终目标1.创建点位管理2.添加数据字典3.配置代码生成信息4.下载代码并导入项目 4.优化菜单——点位管理1.优化区域管理2.增加点位数3. 合作商4.区域管理中添加查看详情功能5.合作商添加点位…

揭秘一下平时我们下载的python库跑到哪里了呢???

&#xff08;阅读之前&#xff0c;祝福大家国庆假期快乐&#xff0c;以及真诚的祝福我们的祖国越来越强大&#xff09;在那天的课上&#xff0c;老师问了我们这样一个问题&#xff1a;你们知道你们平时pip install下载库&#xff0c;下载好了&#xff0c;你们的库是下载到哪里了…

【高频SQL基础50题】16-20

day by day. 目录 1.进店却未进行过交易的顾客 2.项目员工 I 3.销售分析III 4. 判断三角形 5. 电影评分 1.进店却未进行过交易的顾客 连接题。 思路&#xff1a;根据trans表中的visit_id号在 visits表中排除&#xff0c;再将剩下的合并相同客户&#xff08;累加visit…

【API安全】crAPI靶场全解

目录 BOLA Vulnerabilities Challenge 1 - Access details of another user’s vehicle Challenge 2 - Access mechanic reports of other users Broken User Authentication Challenge 3 - Reset the password of a different user Excessive Data Exposure Challenge …

wordpress Contact form 7发件人邮箱设置

此教程仅适用于演示站有留言的主题&#xff0c;演示站没有留言的主题&#xff0c;就别往下看了&#xff0c;免费浪费时间。 使用了Contact form 7插件的简站WordPress主题&#xff0c;在有人留言时&#xff0c;就会发邮件到网站的系统邮箱(一般与管理员邮箱为同一个)里。上面显…

动态规划算法:13.简单多状态 dp 问题_打家劫舍II_C++

目录 题目链接&#xff1a;LCR 090. 打家劫舍 II - 力扣&#xff08;LeetCode&#xff09; 一、题目解析 题目&#xff1a; 解析&#xff1a; 二、算法原理 1、状态表示 2、状态转移方程 状态转移方程推理&#xff1a; 1、i位置状态分析 2、首尾状态分析 3、初始化 d…

《Linux从小白到高手》理论篇(九):Linux的资源监控管理

本篇介绍Linux的资源监控管理。 1、CPU 资源管理 进程调度&#xff1a; Linux 采用公平的进程调度算法&#xff0c;确保每个进程都能获得合理的 CPU 时间。调度算法会根据进程的优先级、等待时间等因素来决定哪个进程获得 CPU 使用权。 可以通过调整进程的优先级来影响其获得…

【数学分析笔记】第4章第2节 导数的意义和性质(1)

4. 微分 4.2 导数的意义与性质 4.2.1 导数在物理中的背景 物体在OS方向上运动&#xff0c;位移函数为 s s ( t ) ss(t) ss(t)&#xff0c;求时刻 t t t的瞬时速度&#xff0c;找一个区间 [ t , t △ t ] [t,t\bigtriangleup t] [t,t△t]&#xff0c;从时刻 t t t变到时刻 t…

架构设计笔记-5-软件工程基础知识-2

知识要点 构件组装是将库中的构件经适当修改后相互连接,或者将它们与当前开发项目中的软件元素连接,最终构成新的目标软件。 构件组装技术大体可分为: 1. 基于功能的组装技术:基于功能的组装技术采用子程序调用和参数传递的方式将构件组装起来。它要求库中的构件以子程序…

产品经理的学习

初学 接需求 画原型 写文档 日常产出 流程图 举例购物的流程 结构图 一个应用的全部功能&#xff0c;用思维导图的方式去罗列出来 竞品分析文档 竞品分类 竞品选择 竞品采集 竞品文档书写 也可以做一个产品的产品结构图 需求文档 干系人 需求方 记录人 产品经理 其他项目干系人…

时序必读论文15|TimeXer:通过外部变量增强Transformer在时间序列预测中的能力

论文标题&#xff1a;TimeXer: Empowering Transformers for Time Series Forecasting with Exogenous Variables 论文链接&#xff1a;https://arxiv.org/abs/2402.19072 前言 仅仅关注内生变量&#xff0c;通常不足以保证准确的预测&#xff0c;外部序列可以为内生变量提供…