一、第一种情况:java.lang.OutOfMemoryError: Java heap space
使用vistualVM分析堆快照如图,可以看到导致OOM的线程是哪一个,类实例所占用的内存的比例也可以看到
点击到OOMObj里边可以看到下边的,垃圾回收的根节点是ArrayList
二、第二种情况:java.lang.StackOverflowError虚拟机栈溢出,最大深度问题
三、第三种情况:java.lang.OutOfMemoryError:PermGen space
希望测试:永久代出现内存溢出,PermGen space,大家看 我把对应的永久代的参数设置为5M,单元测试使用的是String的intern方法,如下:
结果1:GC overhead limit exceed情况如下
并不是我们想要的异常GC overhead limt exceed检查是Hotspot VM 1.6定义的一个策略,通过统计GC时间来预测是否要OOM了,提前抛出异常,防止OOM发生。Sun 官方对此的定义是:“并行/并发回收器在GC回收时间过长时会抛出OutOfMemroyError。过长的定义是,超过98%的时间用来做GC并且回收了不到2%的堆内存。用来避免内存过小造成应用不能正常工作。“通过日志可以看到old区占用过多导致频繁Full GC,最终导致GC overhead limit exceed。
新增-XX:-UseGCOverheadLimit经过调整后参数如下:
结果2:
看一下运行效果,是不是很意外,居然报的是java heap space,还不是我们想要的,堆内存溢出
笔者采用的jdk是1.8,从官方更新看,java8的时候去除PermGen,将其中的方法区移到non-heap中的Metaspace,那是不是意味着原来存储在PermGen的常量会存储在Metaspace,但是从GC日志看Metaspace的大小几乎没有变化,那到底是怎么回事呢?难道String的intern方法不起作用了?原来是在jdk1.6中intern()会将首次遇到的字符串复制到永久代中,返回的是永久代实例的引用,而1.7不在复制实例,只是在常量池中记录首次出现的实例引用,所以会看到Metaspace大小几乎没有变化。
下边例子说明,第一个str1.intern之后返回的实例对象和在java堆中的str1对象比较为true能够说明这一情况,
str2却出现不同的情况,是因为”java”这个字符串太特别了,在执行本次单元测试之前在某一个地方出现过.(具体是哪里笔者并没有探究)
接下来我们就制造一下真实方法区内存溢出:java.lang.OutOfMemoryError: Metaspace
单元测试借助CGLIb动态代理生成大量的class载入内存,如下:
结果3:
可以看到由于我们创建的类在方法区没有被回收,导致Metaspace被撑爆了。
解决运行时OutOfMemoryError,首先你需要检查是否允许GC从PermGen卸载类,JVM的标准配置相当保守,只要类一创建,即使已经没有实例引用它们,其仍将保留在内存中,特别是当应用程序需要动态创建大量的类但其生命周期并不长时,允许JVM卸载类对应用大有助益,你可以通过在启动脚本中添加以下配置参数来实现:
-XX:+CMSClassUnloadingEnabled默认情况下,这个配置是未启用的,如果你启用它,GC将扫描PermGen区并清理已经不再使用的类。但请注意,这个配置只在UseConcMarkSweepGC的情况下生效,如果你使用其他GC算法,比如:ParallelGC或者Serial GC时,这个配置无效。所以使用以上配置时,请配合:-XX:+UseConcMarkSweepGC如果你已经确保JVM可以卸载类,但是仍然出现内存溢出问题,那么你应该继续分析dump文件,当你拿到生成的堆转储文件,并利用像Eclipse Memory Analyzer Toolkit这样的工具来寻找应该卸载却没被卸载的类加载器,然后对该类加载器加载的类进行排查,找到可疑对象,分析使用或者生成这些类的代码,查找产生问题的根源并解决它。(摘自:https://www.jianshu.com/p/2fdee831ed03?utm_campaign=maleskine&utm_content=note&utm_medium=seo_notes&utm_source=recommendation)
四、第四种情况:本机直接内存溢出
五、java.lang.OutOfMemoryError: unable to create new native thread
会有2种情况:
1. 系统内存耗尽,无法为新线程分配内存(笔者内存16G,无论如何设置Xss也不能测出效果)
2. 创建线程数超过了操作系统的限制,笔者mac,如图
测试情况:
插播一下JVM分代划分相关的变化:
(1)附一张图说明jvm内存划分:
(2)字符串常量池的变化:在java7的时候将字符串常量池则移到java heap,字符串常量池被限制在整个应用的堆内存中,在运行时调用String.intern()增加字符串常量不会使永久代OOM了
(3)方法区的变化:java8的时候去除PermGen,将其中的方法区移到non-heap中的Metaspace,Metaspace与PermGen之间最大的区别在于:Metaspace并不在虚拟机中,而是使用本地内存。如果没有使用-XX:MaxMetaspaceSize来设置类的元数据的大小,其最大可利用空间是整个系统内存的可用空间。JVM也可以增加本地内存空间来满足类元数据信息的存储。但是如果没有设置最大值,则可能存在bug导致Metaspace的空间在不停的扩展,会导致机器的内存不足;进而可能出现swap内存被耗尽;最终导致进程直接被系统直接kill掉。如果类元数据的空间占用达到MaxMetaspaceSize设置的值,将会触发对象和类加载器的垃圾回收,在内存分配失败后会抛出:java.lang.OutOfMemoryError: Metaspace space
六、java.lang.OutOfMemoryError:Out of swap space
Java应用程序在启动时会指定所需要的内存大小,可以通过-Xmx和其他类似的启动参数来指定。在JVM请求的总内存大于可用物理内存的情况下,操作系统会将内存中的数据交换到磁盘上去。当应用程序向JVM native heap请求分配内存失败并且native heap也即将耗尽时,JVM会抛出Out of swap space错误。该错误消息中包含分配失败的大小(以字节为单位)和请求失败的原因。(Native Heap Memory是JVM内部使用的Memory,这部分的Memory可以通过JDK提供的JNI的方式去访问,这部分Memory效率很高,但是管理需要自己去做,如果没有把握最好不要使用,以防出现内存泄露问题。JVM 使用Native Heap Memory用来优化代码载入(JTI代码生成),临时对象空间申请,以及JVM内部的一些操作。)
java.lang.OutOfMemoryError:Out of swap space?往往是由操作系统级别的问题引起的,例如:
1.操作系统配置的交换空间不足。
2.系统上的另一个进程消耗所有内存资源。
3.还有可能是本地内存泄漏导致应用程序失败,比如:应用程序调用了native code连续分配内存,但却没有被释放。
解决方案:
升级机器以包含更多内存
优化应用程序以减少其内存占用
七、Out of memory:Kill process or sacrifice child
为了理解这个错误,我们需要补充一点操作系统的基础知识。操作系统是建立在进程的概念之上,这些进程在内核中作业,其中有一个非常特殊的进程,名叫“内存杀手(Out of memory killer)”。当内核检测到系统内存不足时,OOM killer被激活,然后选择一个进程杀掉。哪一个进程这么倒霉呢?选择的算法和想法都很朴实:谁占用内存最多,谁就被干掉。如果你对OOM Killer感兴趣的话,建议你阅读参考资料2中的文章。
解决方案
解决这个问题最有效也是最直接的方法就是升级内存,其他方法诸如:调整OOM Killer配置、水平扩展应用,将内存的负载分摊到若干小实例上..... 我们不建议的做法是增加交换空间,具体原因已经在前文说过。参考资料②中详细的介绍了怎样微调OOM Killer配置以及OOM Killer选择进程算法的实现,建议你参考阅读。
① 想要了解更多PermGen与Metaspace的内容推荐你阅读:
② 如果你对OOM Killer感兴趣的话,强烈建议你阅读这篇文章:
作者:CHEN川
链接:https://www.jianshu.com/p/2fdee831ed03
來源:简书