洞察移动政务小程序助力政府数字化转型,保障数据安全和效率提升
1070
2022-09-11
虚拟机系列:内存溢出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
启动出现错误:
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.
如何处理:
检查程序中使用直接内存的代码是否恰当。检查参数-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小时内删除侵权内容。
发表评论
暂时没有评论,来抢沙发吧~