做者:涯海
创做日期:2019-07-15
专栏地址: 【稳定大于一切】
当 JVM 内存严重不足时,就会抛出 java.lang.OutOfMemoryError 错误。本文总结了常见的 OOM 缘由及其解决方法,以下图所示。若有遗漏或错误,欢迎补充指正。html
提示:若是对 JVM 内存模型和垃圾回收机制不熟悉,推荐阅读 《我们从头至尾说一次 Java 垃圾回收》。java
当堆内存(Heap Space)没有足够空间存放新建立的对象时,就会抛出 java.lang.OutOfMemoryError: Java heap space
错误。(提示:根据实际生产经验,能够对程序日志中的 OutOfMemoryError 配置关键字告警,一经发现,当即处理)。linux
Java heap space
错误产生的常见缘由能够分为如下几类:git
针对大部分状况,一般只须要经过 -Xmx
参数调高 JVM 堆内存空间便可。若是仍然没有解决,能够参考如下状况作进一步处理:github
当 Java 进程花费 98% 以上的时间执行 GC,但只恢复了不到 2% 的内存,且该动做连续重复了 5 次,就会抛出 java.lang.OutOfMemoryError:GC overhead limit exceeded
错误。简单地说,就是应用程序已经基本耗尽了全部可用内存, GC 也没法回收。数据库
此类问题的缘由与解决方案跟 Java heap space
很是相似,能够参考上文。数组
该错误表示永久代(Permanent Generation)已用满,一般是由于加载的 class 数目太多或体积太大。缓存
永久代存储对象主要包括如下几类:服务器
PermGen 的使用量与加载到内存的 class 的数量/大小正相关。数据结构
根据 Permgen space 报错的时机,能够采用不一样的解决方案,以下所示:
-XX:MaxPermSize
启动参数,调大永久代空间。-XX:+CMSClassUnloadingEnabled
和 -XX:+UseConcMarkSweepGC
这两个参数容许 JVM 卸载 class。若是上述方法没法解决,能够经过 jmap 命令 dump 内存对象 jmap -dump:format=b,file=dump.hprof <process-id>
,而后利用 Eclipse MAT 功能逐一分析开销最大的 classloader 和重复 class。
JDK 1.8 使用 Metaspace 替换了永久代(Permanent Generation),该错误表示 Metaspace 已被用满,一般是由于加载的 class 数目太多或体积太大。
此类问题的缘由与解决方法跟 Permgen space
很是相似,能够参考上文。须要特别注意的是调整 Metaspace 空间大小的启动参数为 -XX:MaxMetaspaceSize
。
每一个 Java 线程都须要占用必定的内存空间,当 JVM 向底层操做系统请求建立一个新的 native 线程时,若是没有足够的资源分配就会报此类错误。
JVM 向 OS 请求建立 native 线程失败,就会抛出 Unable to create new native thread
,常见的缘由包括如下几类:
该问题发生的常见过程主要包括如下几步:
java.lang.OutOfMemoryError: Unable to create new native thread
错误。ulimia -a
查看最大线程数限制,使用 ulimit -u xxx
调整最大线程数限制。ulimit -a .... 省略部份内容 ..... max user processes (-u) 16384
该错误表示全部可用的虚拟内存已被耗尽。虚拟内存(Virtual Memory)由物理内存(Physical Memory)和交换空间(Swap Space)两部分组成。当运行时程序请求的虚拟内存溢出时就会报 Out of swap space?
错误。
该错误出现的常见缘由包括如下几类:
jmap -histo:live <pid>
命令,强制执行 Full GC;若是几回执行后内存明显降低,则基本确认为 Direct ByteBuffer 问题。根据错误缘由能够采起以下解决方案:
-XX:MaxDirectMemorySize
调低阈值。有一种内核做业(Kernel Job)名为 Out of Memory Killer,它会在可用内存极低的状况下“杀死”(kill)某些进程。OOM Killer 会对全部进程进行打分,而后将评分较低的进程“杀死”,具体的评分规则能够参考 Surviving the Linux OOM Killer。
不一样于其余的 OOM 错误,Kill process or sacrifice child
错误不是由 JVM 层面触发的,而是由操做系统层面触发的。
默认状况下,Linux 内核容许进程申请的内存总量大于系统可用内存,经过这种“错峰复用”的方式能够更有效的利用系统资源。
然而,这种方式也会无可避免地带来必定的“超卖”风险。例如某些进程持续占用系统内存,而后致使其余进程没有可用内存。此时,系统将自动激活 OOM Killer,寻找评分低的进程,并将其“杀死”,释放内存资源。
JVM 限制了数组的最大长度,该错误表示程序请求建立的数组超过最大长度限制。
JVM 在为数组分配内存前,会检查要分配的数据结构在系统中是否可寻址,一般为 Integer.MAX_VALUE - 2
。
此类问题比较罕见,一般须要检查代码,确认业务是否须要建立如此大的数组,是否能够拆分为多个块,分批执行。
Java 容许应用程序经过 Direct ByteBuffer 直接访问堆外内存,许多高性能程序经过 Direct ByteBuffer 结合内存映射文件(Memory Mapped File)实现高速 IO。
Direct ByteBuffer 的默认大小为 64 MB,一旦使用超出限制,就会抛出 Direct buffer memory
错误。
-XX:MaxDirectMemorySize
调整 Direct ByteBuffer 的上限值。-XX:+DisableExplicitGC
选项,若是有就去掉,由于该参数会使 System.gc()
失效。sun.misc.Cleaner
的 clean()
方法来主动释放被 Direct ByteBuffer 持有的内存空间。【稳定大于一切】打造国内稳定性领域知识库,让没法解决的问题少一点,让世界的肯定性多一点。