虚拟机系列:内存溢出OOM以及解决思路

网友投稿 992 2022-09-11

虚拟机系列:内存溢出OOM以及解决思路

虚拟机系列:内存溢出OOM以及解决思路

内存溢出(OutOfMemoryError,简称OOM)是让程序员头疼的问题,出现这种问题一般是内存空间要被用完了,没有足够的空间供程序使用。而在Java程序中,出现内存溢出的原因也有很多,常见的有堆内存溢出,直接内存溢出,永久区/元空间溢出。

堆溢出

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space

堆溢出这种是最常见的一种,在Java中堆是重要的一个空间,Java的大量对象都是直接在堆上分配的(参考内存分配)。当大量对象占据了堆空间而且都是强引用,使之始终无法被回收,当所有对象大小之和大于参数​​-Xmx​​指定的值时,就会出现溢出了。

导致溢出的原因可能有很多,这里列举下常见的原因:

服务工程中的代码类太多,堆空间不够用,可能在启动的时候就会出现堆溢出的错误。代码中存在循环或者死循环,产生过多的实体对象。数据库查询的时候一次查询大量数据。服务启动的参数-Xmx设置过小,也就是第一点。

例如我们不断往ArrayList中加入对象且无法回收导致出现堆溢出

/*** 启动参数限制最大堆和最小堆:-Xms10m -Xmx10m*/public class Test2 { public static void main(String[] args) { ArrayList list = new ArrayList<>(); for (int i=0;i<10;i++){ list.add(new byte[1024*1024]); } }}

启动出现错误:

com.ajisun.coding.ajisunmybatis.Test2Exception in thread "main" java.lang.OutOfMemoryError: Java heap space at com.ajisun.coding.ajisunmybatis.Test2.main(Test2.java:23)

错误中的 ​​Java heap space​​ 表明了是堆内存的溢出。

如何处理

首选检查代码是否存在循环或者死循环,是否能够不断的创建对象。查看启动参数​​-Xmx​​​和​​-Xms​​ 设置的堆内存是否过小,不足以加载服务中的所有类,可以适当增加。检查代码中是否存在数据库查询,没有分页一次性返回大量数据。还可以通过MAT或者VisualVM工具分析,找到占用大量堆空间的对象,然后做出合理优化。

直接内存溢出

Exception in thread "main" java.lang.OutOfMemoryError: Direct buffer memory

这个问题遇到的一般比较少,直接内存不是运行时数据区的一部分。

Java中NIO(New IO)是支持直接使用直接内存的,可以直接获取一块堆外空间使用,而这块空间是直接向操作系统申请的。直接内存的申请速度一般比堆内存慢,但是其访问速度要快于堆内存,所以如果存在可复用且经常被访问的空间,使用直接内存可以提高系统的性能。但是直接内存没有被Java完全托管,使用不当容易出现溢出的问题。

Java中的DirectByteBuffer就是直接内存申请的,使用unsafe的native方法allocateMemory

配置启动参数​​-XX:MaxDirectMemorySize​​​设置直接内存的最大值,如果不配置此参数则默认最大可用直接内存是​​-Xmx​​的值

/*** 启动参数:-XX:MaxDirectMemorySize=5m*/public class Test2 { public static void main(String[] args) { ByteBuffer bb = ByteBuffer.allocateDirect(1024*1024*6); }}

运行出现错误Direct buffer memory

Exception in thread "main" java.lang.OutOfMemoryError: Direct buffer memory at java.nio.Bits.reserveMemory(Bits.java:694) at java.nio.DirectByteBuffer.(DirectByteBuffer.java:123) at java.nio.ByteBuffer.allocateDirect(ByteBuffer.java:311)

如何处理:

检查程序中使用直接内存的代码是否恰当。检查参数-Xmx和-XX:MaxDirectMemorySize 的大小是否合理,可以根据实际情况调整其大小。

线程过多导致OOM

Exception in thread "main" java.lang.OutOfMemoryError: unable to create new native thread

这个错误是程序中创建的线程过多,而线程需要的空间却不够了。

我们运行程序创建的线程都是需要一定内存的,而机器的内存大小是一定的,当线程的数量过多的时候,就会导致OOM。

如下用死循环不断的创建新的线程。

public class Test2 { public static void main(String[] args) { while(true){ new Thread(new Runnable(){ @Override public void run() { try { Thread.sleep(10000000); } catch(InterruptedException e) { } } }).start(); } }}

运行出现异常,说明系统创建的线程数量已经到达最大值。

Exception in thread "main" java.lang.OutOfMemoryError: unable to create new native thread at java.lang.Thread.start0(Native Method) at java.lang.Thread.start(Thread.java:717) at com.ajisun.coding.ajisunmybatis.Test2.main(Test2.java:39)

如何处理:

首先检查代码,是否有优化的空间,减少不必要的线程创建,或者使用线程池复用线程。减少堆空间的内存,这样操作系统可以预留更多的内存用于线程创建。减少每个线程内存空间的占用,使用​​-Xss​​可以指定栈空间的大小(注意:太小会出现栈溢出)。如果真的需要的线程特别多,但是受到操作系统的限,这种需要修改操作系统的最大线程数。

永久代/元空间溢出

Java.lang.outofMemoryError:PermGen Space( 1.8之前)或者java.lang.OutOfMemoryError: Metaspace( 1.8之后)

这种错误是永久代或者元空间溢出,在jdk1.8之前会出现这种错误,之后hotspot用元空间代替了永久代来存储class信息。如果一个系统在不断的创建新的类(不是对象实例),那么最终会导致元空间溢出的。

如何处理:

增加元空间的大小,设置其对应参数的值​​-XX:MaxMetaspaceSize=512m​​减少系统需要的类的数量,检查是否有不需要的类并且清除掉。使用ClassLoader合理的装载各个类,并定期进行回收。

GC效率低下引起的OOM

java.lang.OutOfMemoryError:GC over head limit exceeded

这个错误比较少见。

Java中主要特点就是垃圾回收,而GC是内存回收的关键,如果效率低下,会严重影响系统的整体性能。如果系统的堆空间太小,那么GC所占的时间就会比较多,并且回收所释放的内存也比较少。根据GC占用系统时间以及内存释放的大小,可以评估出GC的效率,一旦虚拟机认为GC的效率过低,就可能直接抛出OOM异常。

一般情况下,虚拟机会检查以下几种情况来判断效率是否低下:

花在GC上的时间是否超过了98%。老年代释放的内存是否小于2%。eden 区释放的内存是否小于2%。是否连续最近5次GC都出现了上述情况(是同时出现,不是出现一个)。

只有满足上面的所有条件才会出现上面的OOM

如何处理:

可以通过开关​​-XX:-UseGCOverheadLimit​​禁止OOM的产生。这样就会出现堆溢出的错误,然后根据堆溢出的问题来处理。优化代码,检查新生代,老年代中对象是否正常,是否有过多的对象无法释放。dump内存,检查是否存在内存泄露,如果没有,加大内存。

本文讲解了几种内存溢出的情况,以及相对应的解决思路。其中常见的错误是堆溢出和线程过多的OOM。

JVM虚拟机系列历史文章

1. 虚拟机系列:jvm运行时堆内存如何分代;

2. 虚拟机系列:jvm中的垃圾回收算法;

3. 虚拟机系列:jvm运行时数据区域;

4. 虚拟机系列:JVM中对象的创建,内存布局和访问定位;

5. 虚拟机系列:JVM中的垃圾收集器;

6. 虚拟机系列:JVM中的内存分配;

7. 虚拟机系列:搞懂虚拟机的日志和日志参数;

8. 虚拟机系列:虚拟机性能监控基础工具;

9. 虚拟机系列:虚拟机性能监控基础工具-jstat;

10. 虚拟机系列:性能监控可视化工具-JConsole;

11. 虚拟机系列:图形化监控工具-VisualVM;

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:虚拟机系列:字符串String
下一篇:Scrapy中的Request和日志分析(scrapy.request)
相关文章

 发表评论

暂时没有评论,来抢沙发吧~