JVM实战OutOfMemoryError异常

news2025/7/9 12:52:07

目录

Java堆溢出

常见原因:

虚拟机栈和本地方法栈溢出

实验1:虚拟机栈和本地方法栈测试(作为第1点测试程序)

 实验2:(作为第1点测试程序)

运行时常量池和方法区溢出

运行时常量池内存溢出

方法区内存溢出

直接内存溢出

实验1:本地内存的OOM

 实验2:直接通过 Unsafe 类申请本地内存


Java堆溢出

        堆内存中主要存放对象、数组等,只要不断地创建这些对象,并且保证GC Roots到对象之间有可达路径来避免垃圾收集回收机制清除这些对象,当这些对象所占空间超过最大堆容量时,就会产生OutOfMemoryError的异常。

堆内存异常示例如下:

/**
* 设置最大堆最小堆:-Xms20m -Xmx20m
*/
public class HeapOOM {
    static class OOMObject {
    }
    public static void main(String[] args) {
        List<OOMObject> oomObjectList = new ArrayList<>();
        while (true) {
            oomObjectList.add(new OOMObject());
        }
    }
}

运行后会报异常,在堆栈信息中可以看到

        java.lang.OutOfMemoryError: Java heap space 的信息,说明在堆内存空间产生内存溢出的异常。

        新产生的对象最初分配在新生代,新生代满后会进行一次Minor GC,如果Minor GC后空间不足会把该对象和新生代满足条件的对象放入老年代,老年代空间不足时会进行Full GC,之后如果空间还不足以存放新对象则抛出OutOfMemoryError异常。

常见原因:

  • 内存中加载的数据过多,如一次从数据库中取出过多数据;
  • 集合对对象引用过多且使用完后没有清空;
  • 代码中存在死循环或循环产生过多的重复对象;
  • 堆内存分配不合理

虚拟机栈和本地方法栈溢出

        由于HotSpot虚拟机中并不区分虚拟机栈和本地方法栈,因此对于HotSpot来说,-Xoss参数(设置本地方栈大小)虽然存在,但实际上是没有任何效果的,栈容量只能由-Xss参数来设定。

关于虚拟机栈和本地方法栈,在《Java虚拟机规范》中描述了两种异常:

        1)如果线程请求的栈深度大于虚拟机所允许的最大深度,将抛出StackOverflowError异常。

        2)如果虚拟机的栈内存允许动态扩展,当扩展栈容量无法申请到足够的内存时,将抛出OutOfMemoryError异常。

《Java虚拟机规范》明确允许Java虚拟机实现自行选择是否支持栈的动态扩展,而HotSpot虚拟机的选择是不支持扩展,所以除非再创建线程申请内存时就因无法获得足够内存而出现OutOfMemoryError异常,否则在线程运行时是不会因为扩展而导致内存溢出的,只会因为栈容量无法容纳新的栈帧而导致StackOverflowError异常。

以下用两个实验验证一下:(单线程下操作)

  • 是否能让HotSpot虚拟机产生OutOfMemoryError异常:使用-Xss参数减少栈内存容量。
    • 结果:抛出StackOverflowError异常,异常出现时输出的堆栈深度相应缩小。
  • 定义大量的本地变量,增大此方法帧中本地变量表的长度。
    • 结果:抛出StackOverflowError异常,异常出现时输出的堆栈深度相应缩小。

实验1:虚拟机栈和本地方法栈测试(作为第1点测试程序)

/**
* VM Args: -Xss128k
*/
public class JavaVMStackSOF {
    private int stackLength = 1;
    public void stackLeak() {
        stackLength++;
        stackLeak();
    }
    public static void main(String[] args) throws Throwable {
        JavaVMStackSOF oom = new JavaVMStackSOF();
        try {
            oom.stackLeak();
        } catch (Throwable e) {
            System.out.println("stack length:" + oom.stackLength);
            throw e;
        }
    }
}

结果:

 实验2:(作为第1点测试程序)

public class JavaVMStackSOF {
    private static int stackLength = 0;
    public static void test() {
        long unused1, unused2, unused3, unused4, unused5,
        unused6, unused7, unused8, unused9, unused10,
        unused11, unused12, unused13, unused14, unused15,
        unused16, unused17, unused18, unused19, unused20,
        unused21, unused22, unused23, unused24, unused25,
        unused26, unused27, unused28, unused29, unused30,
        unused31, unused32, unused33, unused34, unused35,
        unused36, unused37, unused38, unused39, unused40,
        unused41, unused42, unused43, unused44, unused45,
        unused46, unused47, unused48, unused49, unused50,
        unused51, unused52, unused53, unused54, unused55,
        unused56, unused57, unused58, unused59, unused60,
        unused61, unused62, unused63, unused64, unused65,
        unused66, unused67, unused68, unused69, unused70,
        unused71, unused72, unused73, unused74, unused75,
        unused76, unused77, unused78, unused79, unused80,
        unused81, unused82, unused83, unused84, unused85,
        unused86, unused87, unused88, unused89, unused90,
        unused91, unused92, unused93, unused94, unused95,
        unused96, unused97, unused98, unused99, unused100;
        stackLength ++;
        test();
        unused1 = unused2 = unused3 = unused4 = unused5 =
        unused6 = unused7 = unused8 = unused9 = unused10 =
        unused11 = unused12 = unused13 = unused14 = unused15 =
        unused16 = unused17 = unused18 = unused19 = unused20 =
        unused21 = unused22 = unused23 = unused24 = unused25 =
        unused26 = unused27 = unused28 = unused29 = unused30 =
        unused31 = unused32 = unused33 = unused34 = unused35 =
        unused36 = unused37 = unused38 = unused39 = unused40 =
        unused41 = unused42 = unused43 = unused44 = unused45 =
        unused46 = unused47 = unused48 = unused49 = unused50 =
        unused51 = unused52 = unused53 = unused54 = unused55 =
        unused56 = unused57 = unused58 = unused59 = unused60 =
        unused61 = unused62 = unused63 = unused64 = unused65 =
        unused66 = unused67 = unused68 = unused69 = unused70 =
        unused71 = unused72 = unused73 = unused74 = unused75 =
        unused76 = unused77 = unused78 = unused79 = unused80 =
        unused81 = unused82 = unused83 = unused84 = unused85 =
        unused86 = unused87 = unused88 = unused89 = unused90 =
        unused91 = unused92 = unused93 = unused94 = unused95 =
        unused96 = unused97 = unused98 = unused99 = unused100 = 0;
    }
    public static void main(String[] args) {
        try {
            test();
        }catch (Error e){
            System.out.println("stack length:" + stackLength);
            throw e;
        }
    }
}

结果:

运行时常量池和方法区溢出

        由于运行时常量池是方法区的一部分,所以这两个区域的溢出测试可以放到一起进行。

String::intern() 是一个本地方法,它的作用是如果字符串常量池中已经包含一个等于此String 对象的 字符串,则返回代表池中这个字符串的String 对象的引用;否则,会将此String 对象包含的字符串添加到常量池中,并且返回此String对象的引用。 在 JDK 6 或更早之前的 HotSpot 虚拟机中,常量池都是分配在永久代中,我们可以通过-XX: PermSize和 -XX :MaxPermSize限制永久代的大小,
即可间接限制其中常量池的容量。

运行时常量池内存溢出

/**
* VM Args: -XX:PermSize=6M -XX:MaxPermSize=6M
*/
public class RuntimeConstantPoolOOM {
    public static void main(String[] args) {
        // 使用Set保持着常量池引用, 避免Full GC回收常量池行为
        Set<String> set = new HashSet<String>();
        // 在short范围内足以让6MB的PermSize产生OOM了
        short i = 0;
        while (true) {
            set.add(String.valueOf(i++).intern());
        }
    }
}
运行结果:
Exception in thread "main" java.lang.OutOfMemoryError: PermGen space
at java.lang.String.intern(Native Method)
at org.fenixsoft.oom.RuntimeConstantPoolOOM.main(RuntimeConstantPoolOOM.java: 18)
        从运行结果中可以看到,运行时常量池溢出时,在OutOfMemoryError异常后面跟随的提示信息 是 “PermGen space”说明运行时常量池的确是属于方法区(即JDK 6HotSpot 虚拟机中的永久代) 的 一部分
        而使用JDK 7或更高版本的JDK 来运行这段程序并不会得到相同的结果,无论是在JDK7 中继续使 用-XX:MaxPermSize参数或者在JDK 8及以上版本使用-XX:MaxMeta-spaceSize参数把方法区容量同 样限制在6MB,也都不会重现JDK 6 中的溢出异常 ,循环将一直进行下去,永不停歇。 出现这种变化,是因为自JDK 7 起,原本存放在永久代的字符串常量池被移至Java 堆之中,所以在JDK 7 及以上版本,限制方法区的容量对该测试用例来说是毫无意义的。
        这时候 使用-Xmx参数限制最大堆到6MB就能够看到 以下两种运行结果之一,具体取决于哪里的对象分配时产生了溢出:
// OOM异常一:
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at java.base/java.lang.Integer.toString(Integer.java:440)
at java.base/java.lang.String.valueOf(String.java:3058)
at RuntimeConstantPoolOOM.main(RuntimeConstantPoolOOM.java:12)
// OOM异常二:
//根据Oracle官方文档,默认情况下,如果Java进程花费98%以上的时间执行GC,并且每次只有不到2%的堆被恢复,则JVM抛出此错误
Exception in thread "main" java.lang.OutOfMemoryError: GC overhead limit exceeded
at java.lang.Integer.toString(Integer.java:401)
at java.lang.String.valueOf(String.java:3099)
at com.lagou.unit.RuntimeConstantPoolOOM.main(RuntimeConstantPoolOOM.java:17)

方法区内存溢出

方法区的其他部分的内容,方法区的主要职责是用于存放类型的相关信息,如类名、访问修饰符、常量池、字段描述、方法描述等。

        对于这部分区域的测试,基本的思路是运行时产 生大量的类去填满方法区,直到溢出为止。 虽然直接使用Java SE API也可以动态产生类(如反射时的 GeneratedConstructorAccessor和动态代理等)。

HotSpot还是提供了一 些参数作为元空间的防御措施, 主要包括:
  • -XX:MaxMetaspaceSize :设置元空间最大值,默认是-1 ,即不限制,或者说只受限于本地内存 大小
  • -XX:MetaspaceSize:指定元空间的初始空间大小,以字节为单位,达到该值就会触发垃圾收集进行类型卸载,同时收集器会对该值进行调整:如果释放了大量的空间,就适当降低该值;如果释放了很少的空间,那么在不超过-XX:MaxMetaspaceSize(如果设置了的话)情况下,适当提高该值。
  • XX:MinMetaspaceFreeRatio:作用是在垃圾收集之后控制最小的元空间剩余容量的百分比,可减少因为元空间不足导致的垃圾收集的频率。
  • -XX:Max-MetaspaceFreeRatio,用于控制最 大的元空间剩余容量的百分比。

直接内存溢出

  • 直接内存也可能导致OutofMemoryError异常
  • 由于直接内存在Java堆外,因此它的大小不会直接受限于-Xmx指定的最大堆大小,但是系统内存是有限的,Java堆和直接内存的总和依然受限于操作系统能给出的最大内存
  • 直接内存的缺点为:
    • 分配回收成本较高
    • 不受JVM内存回收管理
  • 直接内存大小可以通过MaxDirectMemorySize设置
  • 如果不指定,默认与堆的最大值-Xmx参数值一致

实验1:本地内存的OOM

/**
 * 本地内存的OOM:  OutOfMemoryError: Direct buffer memory
 */
public class BufferTest1 {
    private static final int BUFFER = 1024 * 1024 * 20; //20MB

    public static void main(String[] args) {
        ArrayList<ByteBuffer> list = new ArrayList<>();

        int count = 0;
        try {
            while(true){
                ByteBuffer byteBuffer = ByteBuffer.allocateDirect(BUFFER);
                list.add(byteBuffer);
                count++;
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        } finally {
            System.out.println(count);
        }

    }
}

本地内存持续增长,直至程序抛出异常:java.lang.OutOfMemoryError: Direct buffer memory

 实验2:直接通过 Unsafe 类申请本地内存

        Unsafe 类在 sun.misc 包下,不属于 Java 标准。

/**
 * 设置JVM参数 :-Xmx20m -XX:MaxDirectMemorySize=10m
 */
public class MaxDirectMemorySizeTest {
    private static final long _1MB = 1024 * 1024;

    public static void main(String[] args) throws IllegalAccessException {
        Field unsafeField = Unsafe.class.getDeclaredFields()[0];
        unsafeField.setAccessible(true);
        Unsafe unsafe = (Unsafe)unsafeField.get(null);
        while(true){
            unsafe.allocateMemory(_1MB);
        }

    }
}

Untitled

 

作者:筱白爱学习!!

欢迎关注转发评论点赞沟通,您的支持是筱白的动力!

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

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

相关文章

模型压缩技术综述

当今&#xff0c;随着深度学习技术的不断进步&#xff0c;越来越多的大型多模态模型被应用于自然语言处理、计算机视觉等领域。然而&#xff0c;这些模型往往需要数十亿甚至上百亿的参数&#xff0c;不仅训练时间长、计算资源消耗大&#xff0c;而且在实际应用中也会受到硬件、…

android sdk分析工具uiautomatorviewer.bat启动报错

位于android sdk目录android-sdk\tools\bin下的分析工具uiautomatorviewer.bat启动报错&#xff1a; uiautomatorviewer.bat -Djava.ext.dirs..\lib\x86_64;..\lib is not supported. Use -classpath instead. Error: Could not create the Java Virtual Machine. Error: A fa…

20张图带你彻底了解ReentrantLock加锁解锁的原理

哈喽大家好&#xff0c;我是阿Q。 最近是上班忙项目&#xff0c;下班带娃&#xff0c;忙的不可开交&#xff0c;连摸鱼的时间都没有了。今天趁假期用图解的方式从源码角度给大家说一下ReentrantLock加锁解锁的全过程。系好安全带&#xff0c;发车了。 简单使用 在聊它的源码…

浅谈Spring6之P和C命名空间注入的区别

基于P命名空间注入 P命名空间注入是简化构造方法的注入 使用P命名空间注入的两个条件 一&#xff1a;xmlns:p"http://www.springframework.org/schema/p" 二&#xff1a;提供set方法class类 Spring_p.xml配置<!--P命名注入底层是set注入><bean id"dogB…

anaconda的linux版本以及jupyter的安装和DataSpell连接linux的jupyter服务器

anaconda安装&#xff1a;官网&#xff1a;https://www.anaconda.com/拷贝下载网址后&#xff0c;在Linux里进行下载&#xff1a;wget https://repo.anaconda.com/archive/Anaconda3-2022.10-Linux-x86_64.sh执行sh&#xff1a;./Anaconda3-2022.10-Linux-x86_64.sh 安装完后&a…

webpack指南(优化篇)——webpack项目优化

系列文章目录 webpack指南&#xff08;基础篇&#xff09;——手把手教你配置webpack 文章目录系列文章目录前言一、概述二、传输性能优化1.代码压缩1.1 css压缩1.2 js压缩1.3 进一步压缩js1.4 压缩html1.5图片压缩2.摇树优化(Tree Shaking)3.作用域提升4.抽取公共模块&#x…

python+selenium自动化测试解决不了的alert问题,使用PyKeyboard解决方法

在python自动化测试中&#xff0c;面对浏览器弹出的对话框&#xff0c;python自带的alert可能解决不了关闭弹框&#xff0c;这时候可以引入PyKeyboard来解决下载并安装pyHook-1.5.1-cp36-cp36m-win_amd64.whl&#xff0c;下载地址https://www.lfd.uci.edu/~gohlke/pythonlibs/&…

你需要知道的云计算常识

云计算常识_基础阶段云计算服务器架构的演进云计算能够提供什么价值&#xff1f;谁为云计算提供服务&#xff1f;计算机的组成部分进制转换云计算 服务器架构的演进 Mon 06Mon 13Mon 20裸金属 虚拟机 云主机(云平台) 容器&#xff08;云原生&#xff09; serviceless(无服务) …

基于VC+MSSQL开发的医院PACS系统全套源代码 强大的三维重建功能

医院PACS系统源码 PACS源码 一套医学影像存储与传输系统&#xff0c;PACS部分主要提供医学影像获取、影像信息网络传递、大容量数据存储、影像显示和处理、影像打印等功能。RIS主要提供分诊登记、叫号、检查报告生成和打印等功能。本套影像存储与传输系统将二者进行无缝对接&a…

CHAPTER 4 文件共享 - Samba

文件共享 - Samba1 Samba1.1 Samba的软件架构1.2 搭建Samba服务器1.3 samba用户管理1. 添加用户2. 修改用户密码3. 删除用户和密码4. 查看samba用户列表5. 查看samba服务器状态1.4 samba共享设置&#xff08;配置文件详解&#xff09;1.5 访问共享目录1. windows访问2. linux客…

机智云目前我用过最便捷的物联网快速开发方案

GE211 MINI DTU上手来看&#xff0c;是一款尺寸比较小巧的模块&#xff0c;适合放置在几乎所有白色家电中&#xff0c;通过ph2.0端子&#xff08;注意不要买错&#xff09;引出了5v、gnd、tx、rx。可以说是非常方便了。下面正式开始我们的接入流程&#xff1a;首先注册一个机智…

PPC902AE101 3BHE010751R0101

PPC902AE101 3BHE010751R0101PPC902AE101 3BHE010751R0101PLC脉冲量的控制目的主要是位置控制、运动控制、轨迹控制等。例如&#xff1a;脉冲数在角度控制中的应用。步进电机驱动器的细分是每圈10000&#xff0c;要求步进电机旋转90度。 那么所要动作的脉冲数值10000/(360/90)2…

如果读了我2011年求职前端开发的酸爽经历,希望你可以鼓起勇气继续向前

今年是2023年&#xff0c;如果你觉得今年找工作很难&#xff0c;狗哥回忆了一下2011年求职前端开发工作的酸爽经历&#xff0c;希望你读了以后可以鼓起勇气&#xff0c;不要迷茫&#xff0c;简历投出去石沉大海的&#xff0c;需要改简历的就赶紧改&#xff0c;刷题不到位的就赶…

美国主机的CMS兼容性:如何选择适合您的CMS?

在如今的数字时代&#xff0c;网站是企业宣传和业务发展的重要方式&#xff0c;而一个好的主机对于网站的性能和稳定性至关重要。如果您正在寻找一个适合您的CMS(内容管理系统)的美国主机&#xff0c;您需要考虑一些关键因素&#xff0c;以确保您选择的主机能够满足您的需求。以…

接上篇《旋转矩阵的作用:世界坐标变换;求解局部坐标系下的局部坐标》,理解欧拉角:内旋;外旋;万向节死锁

目录1、什么是欧拉角、静态欧拉角、动态欧拉角2、内旋、外旋及计算方式3、万向节死锁接上篇《旋转矩阵的作用&#xff1a;世界坐标变换&#xff1b;求解局部坐标系下的局部坐标》&#xff0c;理解欧拉角&#xff1a;内旋&#xff1b;外旋&#xff1b;万向节死锁1、什么是欧拉角…

抢跑数字中国建设,青岛市统计系统考察团赴实在智能调研统计数字员工

当前&#xff0c;数据要素价值不断显现&#xff0c;数字经济正引领着政企业加快数字技术的应用&#xff0c;融通创新工作机制&#xff0c;推进高质量转型。近日&#xff0c;中共中央、国务院印发了《数字中国建设整体布局规划》。《规划》指出&#xff0c;到2025年&#xff0c;…

Rust 开发系列PyO3:Rust与Python的联动编程(上)

前言 Rust语言经常被人误认为是R语言&#xff0c;或者Ruby语言……但是做为近十年来tiobe最出人意料的编程语言&#xff0c;从冷门逐渐变成了明星&#xff0c;不过这次我们不讲Rust入门的内容&#xff0c;我们先来看看它一个很实用的功能——与Python的联动编程。 在正式开始之…

易优cms searchform 搜索标签使用方法

searchform 搜索标签 【基础用法】 标签&#xff1a;searchform 描述&#xff1a;文档标题搜索&#xff0c;默认搜索整站 用法&#xff1a; {eyou:searchform typesonself} <form method"get" action"{$field.action}"> <input …

僵尸(Zombie)进程

文章目录1.僵尸进程2.产生僵尸进程的原因3.利用 wait 函数销毁僵尸进程4.使用 waitpid 函数销毁僵尸进程1.僵尸进程 进程完成工作后&#xff08;执行完 main 函数中的程序后&#xff09;应被销毁&#xff0c;但有时这些进程将变成僵尸进程&#xff0c;占用系统中的重要资源。这…

vue 监测数据改变的原理,添加属性

vue 监测数据改变的原理&#xff0c;添加属性 概况就是vue帮我们处理了&#xff0c;data的数据&#xff0c;加了get和set在生成虚拟dom模板之前&#xff0c; 开始做data数据的生成&#xff0c;get&#xff0c;set vue 提供的api : Vue.set(vm._data.student,‘key’,‘val’)…