JVM实战OutOfMemoryError异常
创始人
2024-05-31 00:50:38
0

目录

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 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 set = new HashSet();// 在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 6的HotSpot虚拟机中的永久代) 的 一部分。         而使用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; //20MBpublic static void main(String[] args) {ArrayList 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

 

作者:筱白爱学习!!

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

相关内容

热门资讯

郑州安卓会议视频系统,高效便捷... 你有没有想过,在繁忙的会议中,如何让每个人都能清晰地看到演示内容呢?今天,就让我带你走进郑州安卓会议...
安卓平板怎么备份系统,全方位备... 你有没有想过,如果你的安卓平板突然“罢工”了,里面的资料怎么办?别急,今天就来教你怎么备份系统,让你...
安卓子系统什么条件,探索安卓子... 你有没有想过,安卓系统中的子系统到底在什么条件下才能发挥出它最大的威力呢?这可是个有趣的话题呢!想象...
安卓定期更新系统,守护安全与流... 你知道吗?安卓系统,这个陪伴我们手机生活的“小助手”,最近可是又长大啦!没错,我又要来跟你聊聊安卓定...
安卓怎么删旧系统,安卓手机系统... 手机用久了,是不是觉得卡得要命?别急,今天就来教你怎么给安卓手机删掉旧系统,让它焕发新生!一、为什么...
安卓系统旺信图标,安卓系统中的... 你有没有注意到手机屏幕上那个小小的旺信图标?它静静地躺在安卓系统的应用列表里,仿佛一个忠诚的小卫士,...
安卓系统刷入ios系统教程,刷... 亲爱的手机控们,你是否曾幻想过,你的安卓手机也能拥有iOS系统的优雅和流畅?别再只是想想了,今天就来...
荣耀安卓系统哪些机型,探索荣耀... 你有没有发现,最近安卓手机圈里有个大热门?那就是荣耀安卓系统的新机型!是不是好奇哪些机型搭载了这款全...
安卓系统绘画工具,创意无限的艺... 你有没有发现,随着智能手机的普及,我们手中的小屏幕已经变成了一个创意无限的小画室呢?没错,就是那个安...
安卓系统手机载屏,解锁屏幕新体... 你有没有发现,现在手机屏幕越来越大,简直就像个小电视!这不,安卓系统手机载屏的风潮已经席卷而来,让我...
安卓系统有onenote吗,便... 你有没有发现,安卓系统里的应用真是五花八门,让人眼花缭乱。不过,说到笔记应用,你是不是也在想,安卓系...
安卓10和系统11,两大系统巅... 你知道吗?最近手机圈可是热闹非凡呢!安卓系统又迎来了大更新,安卓10和系统11的发布,让无数手机爱好...
酷开系统比安卓系统好?,引领智... 你有没有想过,为什么有些人说酷开系统比安卓系统好呢?这可不是空穴来风哦,今天咱们就来好好聊聊这个话题...
安卓系统没有实时模糊,无需实时... 你有没有发现,用安卓手机拍照的时候,有时候照片里的背景模糊得有点让人头疼呢?别急,今天就来聊聊这个让...
安卓系统永久开启adb,尽享便... 你有没有想过,你的安卓手机里隐藏着一个小秘密?没错,就是那个神秘的ADB(Android Debug...
windows平板 安卓双系统... 你有没有想过,拥有一台既能轻松处理工作,又能畅玩游戏的平板电脑是多么美妙的事情呢?想象一边在Wind...
安卓系统快捷打电话,安卓系统一... 你是不是也和我一样,有时候在手机上看到某个号码,突然就想直接打电话给对方呢?别急,今天就来给你揭秘安...
安卓10的国产系统,创新与融合... 你知道吗?最近安卓10的国产系统可是火得一塌糊涂呢!各大手机品牌纷纷推出自家的定制系统,不仅功能强大...
安卓系统桌面股市动态,实时掌握... 亲爱的读者,你是否也像我一样,对安卓系统桌面上的股市动态充满了好奇?那就让我们一起揭开这神秘的面纱,...
安卓系统中文叫什么,从中文命名... 你有没有想过,我们每天离不开的手机,那个小小的屏幕里,竟然藏着那么多的秘密?今天,我就要来揭秘一个你...