【JVM实战】如何防止OOM内存溢出?

Java开发者们最为头疼的问题就是线上常常会抛出OOM异常,有人说,这肯定是内存不够用了啊,扩大内存容量不就可以了嘛,但是内存是需要代价的,不能无限的扩展,虽然JVM为我们管理内存,保证了内存的合理回收,但是如果我们对它的底层了解的够透彻,编写出更加健壮的程序,就可以降低成本,充分利用资源,减少线上故障带来的损失。

在学习如何防止OOM之前,我们首先要知道什么情况下会发生OOM?

1.堆溢出

堆是JVM中很重要的一块内存,绝大部分对象会被分配在堆上,因此堆内存的管理也成为一件很棘手的事情,一般来说,大多数内存溢出都会发生该区域,大量的创建对象,而这些对象又各自持有强引用不能被回收,当堆内存的使用达到Xmx所指定的堆空间大小的时候,溢出错误就自然而然的产生。

当堆内存溢出的时候我们如何解决呢?最为简单明了的方法就是设置参数-Xmx指定一个更大的堆空间。但是我们也不可能一直调大堆内存,因此,我们可以使用一些内存分析工具,比如MAT,Visual VM工具来查看对象的创建情况,查看是否存在内存泄漏,优化代码,回收无用的对象。

2.直接内存溢出

在Java中NIO,我们也可以直接使用堆外内存,也就是直接内存的使用,这部分空间的内存是直接向操作系统申请的,直接内存的申请速度一般要比堆内存慢,但是其访问速度要比堆内存快,因此,对于那些可以复用的,并且会被经常访问的空间,使用直接内存是可以提高系统性能的。但是由于直接内存不受JVM完全管理,若使用不当,也容易触发直接内存溢出,导致宕机。

如果系统的堆内存少有GC发生,而直接内存申请频繁,会比较容易导致直接内存溢出。因为直接内存不一定能够触发GC,所以保证直接内存不溢出的方法是合理地进行Full GC的执行,或者设定一个系统实际可达的-XX:MaxDirectMemorySize的值,这样,当直接内存使用超过该值得时候就会触发GC,从而避免内存溢出问题,不过这样也会因此频繁的GC导致系统执行变慢,应当斟酌设置。

3.过多线程导致OOM

由于每一个线程的开启都要占用系统内存,因此当线程数量太多的时候,也有可能会导致OOM,由于线程的栈空间也是堆外分配的,因此和直接内存非常相似,如果想让系统支持更多的线程,那么就需要使用一个较小的堆空间。另外,我们还可以减少每一个线程所占的内存空间,使用参数-Xss指定线程的栈空间。

4.永久(元数据)区溢出

在JDK1.8之后,永久区被替换为元数据区域,它们的功能都是相似的,保存类的元数据信息。如果一个系统不断地产生新的类,而没有回收,那么最终很有可能会导致永久区溢出。如果要解决永久区溢出的问题,可以从下面几个方面考虑:

  • 增加MaxPermSize的值。
  • 减少系统需要的类的数量。
  • 使用ClassLoader合理地装载各个类,并定期进行回收。

5.GC效率低引起的OOM

如果堆内存空间太小,那么GC所占的时间就会较多,并且回收所释放的内存就会较少,根据GC所占用的系统时间,以及释放内存的大小,虚拟机会评估GC的效率,一旦虚拟机认为GC的效率过低,就有可能直接抛出OOM异常,不过虚拟机不会随意的判定,以下是虚拟机会检查的几种情况:

  1. 花在GC上的时间是否超过了98%。
  2. 老年代释放的内存是否小于2%。
  3. eden区释放的内存是否小于2%。
  4. 是否连续最近5次GC都出现上述几种情况。

虽然虚拟机限制了这么多的条件,但是在绝大部分的场合中,还是会抛出堆溢出的错误。不过这个错误只是为了提示系统的堆内存可能太小,虚拟机并不强制一定要开启这个错误提示。

参考资料:《实战Java虚拟机》

你可能感兴趣的:(JVM)