8 种 Java- 内存溢出之二 -GC overhead limit exceeded

简介: 8 种 Java- 内存溢出之二 -GC overhead limit exceeded

2.1 GC overhead limit exceeded 概述

Java 运行时环境包含一个内建的垃圾收集线程. 在很多其他编程语言中, 开发者需要手动分配和释放内存区域, 以保证释放的内存可以被复用.

但是 Java 应用只需要分配内存. 只要一个特定的内存空间不再使用, 一个单独的叫做 垃圾收集 的线程会清理内存. GC 如何检测特定内存不再使用的内容超出本文范围, 但是你可以信任 GC 可以把这个活做的很好.

java.lang.OutOfMemoryError: GC overhead limit exceeded(GC 开销超过限制)错误意味着 GC 尝试释放内存但是却无法完成任何一件事情. 默认它发生在: JVM 在 GC 中花费超过 98% 的时间,GC 之后, 只有不到 2% 的堆被释放.

java.lang.OutOfMemoryError: GC overhead limit exceeded错误在以下场景会出现: 你的应用消耗了相当多的可用内存, GC 反复尝试清理, 但均以失败告终.

2.2 原因

java.lang.OutOfMemoryError: GC overhead limit exceeded错误就是 JVM 在发信号告诉你: 应用消耗了太多时间在做垃圾收集, 而且还收效甚微. 默认情况的配置是 JVM 会在以下情况下抛出该错误: JVM 在 GC 中花费超过 98% 的时间,GC 之后, 只有不到 2% 的堆被释放.

如果这个 GC 开销限制不存在,会发生什么情况? 需要注意的是 java.lang.OutOfMemoryError: GC overhead limit exceeded 错误只有当在几次 GC 周期之后, 只有 2% 的内存被释放的情况下才会抛出. 这意味着只有很少量的内存 GC 能够清理, 而且还会再次被迅速填满, 导致 GC 再次开始清理线程. 这形成了一个恶性循环,CPU 100% 在忙于 GC,没有实际工作可做. 应用最终用户面临着极端响应慢的情况 – 本来几毫秒就能完成的操作需要花费几分钟来完成.

所以, java.lang.OutOfMemoryError: GC overhead limit exceeded消息是一个实现 快速失败 (fail-fast) 原则的相当好的案例.

2.3 案例

在下列案例中, 我们通过初始化一个 Map 并且在一个无限循环里增加一个 key-value 对到该 Map 来创建一个 GC overhead limit exceeded 错误:

class Wrapper {
    public static void main(String args[]) throws Exception {
        Map map = System.getProperties();
        Random r = new Random();
        while (true) {
            map.put(r.nextInt(), "value");
        }
    }
}
JAVA

正如你可能猜到的那样,这是不可能结束的. 事实上, 当我们使用以下语句运行:

java -Xmx100m -XX:+UseParallelGC Wrapper

我们不久就会看到 java.lang.OutOfMemoryError: GC overhead limit exceeded 信息. 但是上边的例子很棘手. 当选择不同的 Java heap size 或者不同的 GC 策略, 在 Mac OS X 10.9.2, Hotspot 1.7.0_45 上死法各不相同. 例如, 当我用更小的 Java heap size 运行:

java -Xmx10m -XX:+UseParallelGC Wrapper

该应用会抛出一个更常见的 java.lang.OutOfMemoryError: Java heap space 消息. 当我使用不同的 GC 策略运行它, 比如-XX:+UseConcMarkSweepGC-XX:+UseG1GC, 错误被默认的异常 handler 捕获, 并且在这种情况下,因为堆耗尽, 堆栈跟踪甚至不能在异常创建中被填充。

这些变化确实是很好的例子,说明在资源受限的情况下你不能预测你的应用程序将会以怎样的方式死亡,所以不要以你的期望为基础寄托在要完成的具体操作序列上。

2.4 解决办法

作为一个半开玩笑的解决方案, 如果你希望避免 java.lang.OutOfMemoryError: GC overhead limit exceeded 消息, 可以在启动脚本里增加下列信息:

-XX:-UseGCOverheadLimit

强烈建议不要使用这个参数 – 这只是饮鸩止渴: 最终应用会耗尽内存, 需要修复. 指定这个参数只是用一个更常见的消息java.lang.OutOfMemoryError: Java heap space 掩盖了 java.lang.OutOfMemoryError: GC overhead limit exceeded 错误.

另一种方式(临时的), 是给 JVM 进程更多的内存. 再次说明, 加这个很简单,但是最终仍会导致错误:

java.lang.OutOfMemoryError: Java heap space

在上边例子中, 给 Java 进程 1GB 的 heap. 增加这个值会解决 GC 开销限制问题如果你的应用先是出现内存不足. 但是如果你希望确保你已经解决了潜在的问题而不是掩盖 java.lang.OutOfMemoryError: GC overhead limit exceeded 症状, 你不应该仅止于此. 对于这种情况, 联系我就是最好的方式(@ ̄ー ̄@). 当然你手头上也有不同的工具可供选择, 如: profilers 和内存 dump 分析工具. 但要准备好投入大量的时间, 而且要意识到这一点: 工具会对你的 Java 运行时造成了巨大的开销,因此它们不适合生产环境使用。

对于这种问题, 其实 Dynatrace 也会进行告警. 我这有一篇分析的案例供参考.

相关文章
|
9天前
|
存储 Java 编译器
Java内存区域详解
Java内存区域详解
25 0
Java内存区域详解
|
13天前
|
监控 算法 Java
Java GC调优详解
Java GC调优详解
28 0
|
19天前
|
缓存 算法 Java
Java内存管理与调优:释放应用潜能的关键
【4月更文挑战第2天】Java内存管理关乎性能与稳定性。理解JVM内存结构,如堆和栈,是优化基础。内存泄漏是常见问题,需谨慎管理对象生命周期,并使用工具如VisualVM检测。有效字符串处理、选择合适数据结构和算法能提升效率。垃圾回收自动回收内存,但策略调整影响性能,如选择不同类型的垃圾回收器。其他优化包括调整堆大小、使用对象池和缓存。掌握这些技巧,开发者能优化应用,提升系统性能。
|
15天前
|
缓存 安全 Java
Java并发编程进阶:深入理解Java内存模型
【4月更文挑战第6天】Java内存模型(JMM)是多线程编程的关键,定义了线程间共享变量读写的规则,确保数据一致性和可见性。主要包括原子性、可见性和有序性三大特性。Happens-Before原则规定操作顺序,内存屏障和锁则保障这些原则的实施。理解JMM和相关机制对于编写线程安全、高性能的Java并发程序至关重要。
|
23天前
|
缓存 Java C#
【JVM故障问题排查心得】「Java技术体系方向」Java虚拟机内存优化之虚拟机参数调优原理介绍(一)
【JVM故障问题排查心得】「Java技术体系方向」Java虚拟机内存优化之虚拟机参数调优原理介绍
61 0
|
5天前
|
存储 缓存 监控
Java内存管理:垃圾回收与内存泄漏
【4月更文挑战第16天】本文探讨了Java的内存管理机制,重点在于垃圾回收和内存泄漏。垃圾回收通过标记-清除过程回收无用对象,Java提供了多种GC类型,如Serial、Parallel、CMS和G1。内存泄漏导致内存无法释放,常见原因包括静态集合、监听器、内部类、未关闭资源和缓存。内存泄漏影响性能,可能导致应用崩溃。避免内存泄漏的策略包括代码审查、使用分析工具、合理设计和及时释放资源。理解这些原理对开发高性能Java应用至关重要。
|
13天前
|
存储 缓存 安全
【企业级理解】高效并发之Java内存模型
【企业级理解】高效并发之Java内存模型
|
20天前
|
Java
java中jar启动设置内存大小java -jar 设置堆栈内存大小
java中jar启动设置内存大小java -jar 设置堆栈内存大小
11 1
|
20天前
|
缓存 算法 Java
Java内存管理:优化性能和避免内存泄漏的关键技巧
综上所述,通过合适的数据结构选择、资源释放、对象复用、引用管理等技巧,可以优化Java程序的性能并避免内存泄漏问题。
25 5
|
23天前
|
监控 网络协议 NoSQL
java线上排查OOM内存溢出
java线上排查OOM内存溢出
21 0