Fork me on Github
欢迎来到zhang-shuai的博客
zhangshuai
晓看天色暮看云,行也思君,坐也思君

java.lang.OutOfMemoryError 解决方法

java.lang.OutOfMemoryError错误:

1. Java heap space

2. GC overhead limit exceeded

3. GC overhead limit exceeded

4. Metaspace

5. Unable to create new native thread

6. Out of swap space

7. Requested array size exceeds VM limit

8. nativeGetNewTLA

1. Java heap space

原因分析

heap和permgen的最大内存大小, 由JVM启动参数 -Xmx-XX:MaxPermSize 指定. 如果没有明确指定, 则根据平台类型(OS版本+ JVM版本)和物理内存的大小来确定。

假如在创建新的对象时, 堆内存中的空间不足以存放新创建的对象, 就会引发java.lang.OutOfMemoryError: Java heap space 错误。

不管机器上还没有空闲的物理内存, 只要堆内存使用量达到最大内存限制,就会抛出 java.lang.OutOfMemoryError: Java heap space 错误。

产生 java.lang.OutOfMemoryError: Java heap space 错误的原因, 很多时候, 就类似于将 XXL 号的对象,往 S 号的 Java heap space 里面塞。其实清楚了原因, 就很容易解决对不对? 只要增加堆内存的大小, 程序就能正常运行. 另外还有一些比较复杂的情况, 主要是由代码问题导致的:

 

  • 超出预期的访问量/数据量。 应用系统设计时,一般是有 “容量” 定义的, 部署这么多机器, 用来处理一定量的数据/业务。 如果访问量突然飙升, 超过预期的阈值, 类似于时间坐标系中针尖形状的图谱, 那么在峰值所在的时间段, 程序很可能就会卡死、并触发 java.lang.OutOfMemoryError: Java heap space 错误。

 

 

  • 内存泄露(Memory leak). 这也是一种经常出现的情形。由于代码中的某些错误, 导致系统占用的内存越来越多. 如果某个方法/某段代码存在内存泄漏的, 每执行一次, 就会(有更多的垃圾对象)占用更多的内存. 随着运行时间的推移, 泄漏的对象耗光了堆中的所有内存, 那么 java.lang.OutOfMemoryError: Java heap space 错误就爆发了。

 

解决方案

如果设置的最大内存不满足程序的正常运行, 只需要增大堆内存即可,   -Xmx1024m

2. GC overhead limit exceeded

原因分析

JVM抛出 java.lang.OutOfMemoryError: GC overhead limit exceeded 错误就是发出了这样的信号: 执行垃圾收集的时间比例太大, 有效的运算量太小. 默认情况下, 如果GC花费的时间超过 98%, 并且GC回收的内存少于 2%, JVM就会抛出这个错误。

注意, java.lang.OutOfMemoryError: GC overhead limit exceeded 错误只在连续多次 GC 都只回收了不到2%的极端情况下才会抛出。假如不抛出 GC overhead limit 错误会发生什么情况呢? 那就是GC清理的这么点内存很快会再次填满, 迫使GC再次执行. 这样就形成恶性循环, CPU使用率一直是100%, 而GC却没有任何成果. 系统用户就会看到系统卡死 - 以前只需要几毫秒的操作, 现在需要好几分钟才能完成。

解决方案

有一种应付了事的解决方案, 就是不想抛出 “java.lang.OutOfMemoryError: GC overhead limit exceeded” 错误信息, 则添加下面启动参数: -XX:-UseGCOverheadLimit

3. GC overhead limit exceeded

原因分析

在JDK1.7及之前的版本, 永久代(permanent generation) 主要用于存储加载/缓存到内存中的 class 定义, 包括 class 的 名称(name), 字段(fields), 方法(methods)和字节码(method bytecode); 以及常量池(constant pool information); 对象数组(object arrays)/类型数组(type arrays)所关联的 class, 还有 JIT 编译器优化后的class信息等。

很容易看出, PermGen 的使用量和JVM加载到内存中的 class 数量/大小有关。可以说 java.lang.OutOfMemoryError: PermGen space 的主要原因, 是加载到内存中的 class 数量太多或体积太大。

解决方案

1. 解决程序启动时产生的 OutOfMemoryError

在程序启动时, 如果 PermGen 耗尽而产生 OutOfMemoryError 错误, 那很容易解决. 增加 PermGen 的大小, 让程序拥有更多的内存来加载 class 即可. 修改 -XX:MaxPermSize 启动参数, 类似下面这样: java -XX:MaxPermSize=512m com.yourcompany.YourClass

2. 解决 redeploy 时产生的 OutOfMemoryError

我们可以进行堆转储分析(heap dump analysis) —— 在 redeploy 之后, 执行堆转储, 类似下面这样: jmap -dump:format=b,file=dump.hprof <process-id>

3. 解决运行时产生的 OutOfMemoryError

如果在运行的过程中发生 OutOfMemoryError, 首先需要确认 GC是否能从PermGen中卸载class。 官方的JVM在这方面是相当的保守(在加载class之后,就一直让其驻留在内存中,即使这个类不再被使用). 但是, 现代的应用程序在运行过程中, 会动态创建大量的class, 而这些class的生命周期基本上都很短暂, 旧版本的JVM 不能很好地处理这些问题。那么我们就需要允许JVM卸载class。使用下面的启动参数: -XX:+CMSClassUnloadingEnabled

4. Metaspace

原因分析

从Java 8开始,内存结构发生重大改变, 不再使用Permgen, 而是引入一个新的空间: Metaspace. 这种改变基于多方面的考虑, 部分原因列举如下:

 

 

  • 为了 GC 性能 的提升, 使得垃圾收集过程中的并发阶段不再 停顿, 另外对 metadata 进行特定的遍历(specific iterators)。
  • G1垃圾收集器 的并发 class unloading 进行深度优化。

在Java8中,将之前 PermGen 中的所有内容, 都移到了 Metaspace 空间。例如: class 名称, 字段, 方法, 字节码, 常量池, JIT优化代码, 等等。

Metaspace 的使用量与JVM加载到内存中的 class 数量/大小有关。可以说, java.lang.OutOfMemoryError: Metaspace 错误的主要原因, 是加载到内存中的 class 数量太多或者体积太大

解决方案

如果抛出与 Metaspace 有关的 OutOfMemoryError , 第一解决方案是增加 Metaspace 的大小. 使用下面这样的启动参数: -XX:MaxMetaspaceSize=512m

5. Unable to create new native thread

原因分析

JVM向操作系统申请创建新的 native thread(原生线程)时, 就有可能会碰到 java.lang.OutOfMemoryError: Unable to create new native thread 错误. 如果底层操作系统创建新的 native thread 失败, JVM就会抛出相应的OutOfMemoryError. 原生线程的数量受到具体环境的限制, 通过一些测试用例可以找出这些限制, 请参考下文的示例. 但总体来说, 导致 java.lang.OutOfMemoryError: Unable to create new native thread 错误的场景大多经历以下这些阶段:

 

  • Java程序向JVM请求创建一个新的Java线程;

 

  • JVM本地代码(native code)代理该请求, 尝试创建一个操作系统级别的 native thread(原生线程);
  • 操作系统尝试创建一个新的native thread, 需要同时分配一些内存给该线程;
  • 如果操作系统的虚拟内存已耗尽, 或者是受到32位进程的地址空间限制(约2-4GB), OS就会拒绝本地内存分配;
  • JVM抛出 java.lang.OutOfMemoryError: Unable to create new native thread 错误。

解决方案

有时可以修改系统限制来避开 Unable to create new native thread 问题. 假如JVM受到用户空间(user space)文件数量的限制, 像下面这样,就应该想办法增大这个值:

[root@dev ~]# ulimit -a
    core file size          (blocks, -c) 0
    ...... 省略部分内容 ......
    max user processes              (-u) 1800

6. Out of swap space

原因分析

如果 native heap 内存耗尽, 内存分配时, JVM 就会抛出 java.lang.OutOfmemoryError: Out of swap space? 错误消息, 这个消息告诉用户, 请求分配内存的操作失败了。

Java进程使用了虚拟内存才会发生这个错误。 对 Java的垃圾收集 来说这是很难应付的场景。即使现代的 GC算法 很先进, 但虚拟内存交换引发的系统延迟, 会让 GC暂停时间 膨胀到令人难以容忍的地步。

通常是操作系统层面的原因导致 java.lang.OutOfMemoryError: Out of swap space? 问题, 例如:

  • 操作系统的交换空间太小。
  • 机器上的某个进程耗光了所有的内存资源。

当然也可能是应用程序的本地内存泄漏(native leak)引起的, 例如, 某个程序/库不断地申请本地内存,却不进行释放。

解决方案

第一种, 也是最简单的方法, 增加虚拟内存(swap space) 的大小. 各操作系统的设置方法不太一样, 比如Linux,可以使用下面的命令设置:

swapoff -a
dd if=/dev/zero of=swapfile bs=1024 count=655360
mkswap swapfile
swapon swapfile

7. Requested array size exceeds VM limit

原因分析

这个错误是由JVM中的本地代码抛出的. 在真正为数组分配内存之前, JVM会执行一项检查: 要分配的数据结构在该平台是否可以寻址(addressable). 当然, 这个错误比你所想的还要少见得多。

一般很少看到这个错误, 因为Java使用 int 类型作为数组的下标(index, 索引)。在Java中, int类型的最大值为 2^31 – 1 = 2,147,483,647。大多数平台的限制都约等于这个值 —— 例如在 64位的 MB Pro 上, Java 1.7 平台可以分配长度为 2,147,483,645, 以及 Integer.MAX_VALUE-2) 的数组。

再增加一点点长度, 变成 Integer.MAX_VALUE-1 时, 就会抛出我们所熟知的 OutOfMemoryErrorException in thread "main" java.lang.OutOfMemoryError: Requested array size exceeds VM limit`

在有的平台上, 这个最大限制可能还会更小一些, 例如在32位Linux, OpenJDK 6 上面, 数组长度大约在 11亿左右(约2^30) 就会抛出 “java.lang.OutOfMemoryError: Requested array size exceeds VM limit“ 错误。要找出具体的限制值, 

解决方案

发生 java.lang.OutOfMemoryError: Requested array size exceeds VM limit 错误的原因可能是:

  • 数组太大, 最终长度超过平台限制值, 但小于 Integer.MAX_INT
  • 为了测试系统限制, 故意分配长度大于 2^31-1 的数组。

第一种情况, 需要检查业务代码, 确认是否真的需要那么大的数组。如果可以减小数组长度, 那就万事大吉. 如果不行,可能需要把数据拆分为多个块, 然后根据需要按批次加载。

如果是第二种情况, 请记住, Java 数组用 int 值作为索引。所以数组元素不能超过 2^31-1 个. 实际上, 代码在编译阶段就会报错,提示信息为 “error: integer number too large”。

8. nativeGetNewTLA

weblogic服务器内存溢出,经常报 java.lang.OutOfMemoryError: nativeGetNewTLA。 经查是由于 weblogic 使用 jrockit jvm时才会出现这样的问题。网上查了好久解决方案,给出的都是 调整 -XXtlaSize 这个参数。按照网上的说法,我将 -XXtlaSize 调成512M依然出错。
郁闷之余,直接改weblogic的jvm, 改完之后就没有再报错了。
修改方法:在startWeblgoic.sh 头上加一句 JAVA_VENDOR=Sun 即可。

 

 

 

 

 

 

 
posted @ 2021-09-09 17:02  咘網鶵釁  阅读(2650)  评论(0编辑  收藏  举报