「OOM」Java heap space原因与解决

JVM的OOM分为多种情况,下面会针对JAVA.lang.OutOfMemoryError: Java heap space这种情况讲解一下发生的原因与解决方案 。
在JAVA应用启动时,会限制应用的使用空间 。也就说,任何一个JAVA应用,都只能使用有限的内存空间 。
【「OOM」Java heap space原因与解决】JAVA的内存空间在JDK7及以前划分为堆与永久代 。在JDK8之后移除了永久代,采用元空间来代替 。
在启动时,通过指定JVM参数:`-Xmx` 来设置可使用的最大堆大小 。如果没有显式的设置,则系统上默认为物理内存的1/4(根据物理内存的不同情况有不同的分配规则 。但是普遍可以认为是1/4) 。
发生java.lang.OutOfMemoryError: Java heap space异常时,代表着应用尝试从堆上申请一个区域时,堆没有可配的空间 。(注:可能有可使用的物理内存,但是没有已经达到了JAVA应用可分配的内存大小)

JVM是很智能的,在即将发生OOM时,会进行一次FullGC以回收可回收的对象来释放空间 。如果FullGC之后还是没有可满足大小的空间分配,才抛出java.lang.OutOfMemoryError: Java heap space 。
java.lang.OutOfMemoryError: Java heap space正常是怎么发生的呢?
  • 突发高峰期:程序在正常的用户量和一定数据量时运行正常 。但是,在某个高峰时导致超出预期阈值,内存存活对象使用空间的量超出最大堆,并且无法回收 。
  • 内存泄露: 由于编程错误导致应用程序不再需要的对象(数据)一直被持有引用,导致无法被回收 。随着时间的推移,泄露的内存对象占用了所有的可用堆空间 。
分配合理的足够内存最简单的解决方法就给JVM分配足够大的内存来满足运行程序的需求 。
但是,需要注意在内存泄漏的情况下,分配再大的内存也只是推迟了java.lang.OutOfMemoryError: Java heap space的发生 。
而且,加大了JVM堆内存,也会增加在GC时的暂停时间(STW),影响程序的吞吐量,增加延迟 。
如何分配一个合理的内存空间,是需要针对GC进行优化的 。也就是常说的JVM调优 。
JVM调优可以参考:「JVM」GC——调优介绍
那么,如何调整通过分配JAVA堆空间来解决问题呢?
首先,需要了解以下这些问题:
  1. 哪些对象占用了大量的堆空间
  2. 在哪些代码中创建了这些对象
上述的问题可以通过JVM自身的jmap来dump出运行时的堆栈信息 。然后通过如:MAT,JProfiler,jconsole等空间来进行内存对象占用的跟踪 。
MAT使用可以参考:[JVM] MAT进阶使用
当然,这种方式是比较原始的方式 。建议通过如:Plumbr等JVM监控工具来跟踪问题 。
「OOM」Java heap space原因与解决

文章插图
Plumbr的报告信息
以上图的监控举例简要说明一下如何适当的进行堆空间的大小分配 。
上图所示中,可以得到如下信息:
  • 所有相关对象的整个GCRoot引用
  • 内存消耗最多的对象:

「OOM」Java heap space原因与解决

文章插图
 
  • 这些对象在代码中的分配位置:

「OOM」Java heap space原因与解决

文章插图
 
根据上述的信息, 我们可以得到这样的猜想:
这个程序的需要的运行空间超过248MB,并且是无法在一定时间内释放被回收 。那么,按JVM调优的思路,建议分配的最大堆大小为:老年代活跃数据大小 * 3~4倍 。
所以,我们第一次调整时,可以分配:248 * 4 = 992 。
由于堆大小的无法确认,所以第一次调整直接调整为:-Xmx1024m 。
单位:
-Xmx1024 即配置1024b = 1kb
-Xmx1024k 即配置1mb
-Xmx1024m 即配置1gb
-Xmx1g 即配置1gb
建议:
在配置-Xmx时,应该将-Xms也配置成相同大小 。避免JVM需要动态调整堆空间大小带来的性能影响 。




    推荐阅读