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

 

作者:筱白爱学习!!

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

相关内容

热门资讯

电视安卓系统哪个品牌好,哪家品... 你有没有想过,家里的电视是不是该升级换代了呢?现在市面上电视品牌琳琅满目,各种操作系统也是让人眼花缭...
安卓会员管理系统怎么用,提升服... 你有没有想过,手机里那些你爱不释手的APP,背后其实有个强大的会员管理系统在默默支持呢?没错,就是那...
安卓系统软件使用技巧,解锁软件... 你有没有发现,用安卓手机的时候,总有一些小技巧能让你玩得更溜?别小看了这些小细节,它们可是能让你的手...
安卓系统提示音替换 你知道吗?手机里那个时不时响起的提示音,有时候真的能让人心情大好,有时候又让人抓狂不已。今天,就让我...
安卓开机不了系统更新 手机突然开不了机,系统更新还卡在那里,这可真是让人头疼的问题啊!你是不是也遇到了这种情况?别急,今天...
安卓系统中微信视频,安卓系统下... 你有没有发现,现在用手机聊天,视频通话简直成了标配!尤其是咱们安卓系统的小伙伴们,微信视频功能更是用...
安卓系统是服务器,服务器端的智... 你知道吗?在科技的世界里,安卓系统可是个超级明星呢!它不仅仅是个手机操作系统,竟然还能成为服务器的得...
pc电脑安卓系统下载软件,轻松... 你有没有想过,你的PC电脑上安装了安卓系统,是不是瞬间觉得世界都大不一样了呢?没错,就是那种“一机在...
电影院购票系统安卓,便捷观影新... 你有没有想过,在繁忙的生活中,一部好电影就像是一剂强心针,能瞬间让你放松心情?而我今天要和你分享的,...
安卓系统可以写程序? 你有没有想过,安卓系统竟然也能写程序呢?没错,你没听错!这个我们日常使用的智能手机操作系统,竟然有着...
安卓系统架构书籍推荐,权威书籍... 你有没有想过,想要深入了解安卓系统架构,却不知道从何下手?别急,今天我就要给你推荐几本超级实用的书籍...
安卓系统看到的炸弹,技术解析与... 安卓系统看到的炸弹——揭秘手机中的隐形威胁在数字化时代,智能手机已经成为我们生活中不可或缺的一部分。...
鸿蒙系统有安卓文件,畅享多平台... 你知道吗?最近在科技圈里,有个大新闻可是闹得沸沸扬扬的,那就是鸿蒙系统竟然有了安卓文件!是不是觉得有...
宝马安卓车机系统切换,驾驭未来... 你有没有发现,现在的汽车越来越智能了?尤其是那些豪华品牌,比如宝马,它们的内饰里那个大屏幕,简直就像...
p30退回安卓系统 你有没有听说最近P30的用户们都在忙活一件大事?没错,就是他们的手机要退回安卓系统啦!这可不是一个简...
oppoa57安卓原生系统,原... 你有没有发现,最近OPPO A57这款手机在安卓原生系统上的表现真是让人眼前一亮呢?今天,就让我带你...
安卓系统输入法联想,安卓系统输... 你有没有发现,手机上的输入法真的是个神奇的小助手呢?尤其是安卓系统的输入法,简直就是智能生活的点睛之...
怎么进入安卓刷机系统,安卓刷机... 亲爱的手机控们,你是否曾对安卓手机的刷机系统充满好奇?想要解锁手机潜能,体验全新的系统魅力?别急,今...
安卓系统程序有病毒 你知道吗?在这个数字化时代,手机已经成了我们生活中不可或缺的好伙伴。但是,你知道吗?即使是安卓系统,...
奥迪中控安卓系统下载,畅享智能... 你有没有发现,现在汽车的中控系统越来越智能了?尤其是奥迪这种豪华品牌,他们的中控系统简直就是科技与艺...