type
status
date
slug
summary
tags
category
icon
password
OOM 的原因只有一个,就是内存不够用了。至于是哪里的内存不够用,我们需要深入分析一下。在排查问题的过程中我们重点关注内存相关的信息。

1、使用原生命令排查

1、找出 java 进程的 pid。
2、查看堆内存使用和 GC 情况。
查看新生代(S0、S1、Eden区)、老年代的内存使用率,以及 Young GC、Full GC 的耗时和次数。
  • S0:新生代中 S0 区已使用空间的百分比
  • S1:新生代中 S1 区已使用空间的百分比
  • E:新生代已使用空间的百分比
  • O:老年代已使用空间的百分比
  • M:元空间已使用空间的百分比
  • CCS:压缩空间已使用空间的百分比
  • YGC:从应用程序启动到当前,发生 Young GC 的次数
  • YGCT:从应用程序启动到当前,Young GC 所用的时间,单位秒
  • FGC:从应用程序启动到当前,发生 Full GC 的次数
  • FGCT:从应用程序启动到当前,Full GC 所用的时间
  • GCT:从应用程序启动到当前,用于垃圾回收的总时间,单位秒
3、查看整体内存使用情况
查看元空间、堆内存的使用情况
查看每个类的实例数量和内存占用
查看直接内存的使用情况(需要提前添加 JVM 参数 -XX:+UnlockDiagnosticVMOptions -XX:NativeMemoryTracking=detail -XX:+PrintNMTStatistics
4、生成 dump 文件,然后使用 MAT 等工具分析内存。
建议提前添加 JVM 参数,在 OOM 的时候自动生成 dump 文件,这样可以保留事故现场。
  • -XX:+HeapDumpOnOutOfMemoryError:当堆内存溢出时触发生成内存快照文件,通常会搭配 -XX:HeapDumpPath 一起使用。注意触发条件是 java.lang.OutOfMemoryError: Java heap space。所以 System.gc() 和 Full GC 并不会触发生成内存快照文件。
  • -XX:HeapDumpPath=/tmp/heap-dump.hprof :指定快照的输出路径,同时可以指定快照文件名。默认快照文件名是 java_<pid><date><time>_heapdump.hprof
  • -XX:OnOutOfMemoryError :发生内存溢出后执行的脚本。如果使用 K8S 部署 java 服务,发生 OOM 之后服务所在的 pod 有可能会自动销毁重建,里面的 dump 文件有可能会丢失。这时候这个参数就很有用了,可以在 pod 被销毁之前执行脚本把 dump 文件保存下来。

2、使用Arthas排查

1、安装并启动 Arthas
在生产环境中,我们一般是选择其中一台机器开启 Arthas 监控,因为使用 Arthas 对系统性能会有一定影响。
Arthas 会显示当前运行的 Java 进程列表,选择要诊断的 Java 进程,然后就会连接到目标进程。
2、用 dashboard 命令查看系统概况
连接到目标进程后,使用 dashboard 命令查看系统的整体性能概况,包括 CPU 使用情况。
3、使用 memory 命令查看内存使用情况
多次运行memory命令并观察变化,特别是注意那些持续增长而没有减少的内存区域。这些区域很可能是内存泄漏的源头。
4、生成 dump 文件。

3、紧急处理

  • 先确定确认是不是程序问题,如果是程序问题,例如前一天或者最近有新版本发布或者依赖域的服务有新版本发布,第一时间回滚镜像。
  • 如果不是程序问题,就先申请紧急扩容,增加机器。

4、原因分析

Full GC 通常有以下原因,可以根据异常情况对号入座:
  1. 调用 System.gc() 时(调用后并不会立即发生 FGC,后面会在某个时间点发生),操作系统建议执行 Full GC( -XX:+DisableExplicitGC 可禁用 )。
  1. 老年代的可用空间不足时。
  1. 方法区空间不足时,或 Metaspace Space 使用达到 MetaspaceSize 但未达到 MaxMetaspaceSize 阈值,大多情况下扩容都会触发。
  1. Concurrent Mode Failure。如果老年代使用了 CMS 垃圾回收器,因为 CMS 垃圾回收器的垃圾回收线程和用户线程是并发执行的,在 CMS GC 的过程中,如果新生代 Survivor 空间放不下,需要放入老年代,而老年代也放不下,或者用户直接把对象放入老年代放不下,就会报 Concurrent Mode Failure。
  1. Promotion Failed:通过 Minor GC 后进入老年代的平均大小大于老年代的可用内存时。由 Eden 区、From Survior 区向 To Survior 区复制时,对象大小大于 To Survior 区可用内存,则把该对象转存到老年代,且老年代的可用内存小于该对象大小时。
  1. 执行 jmap -histo:live 或者 jmap -dump:live

4.1 Java heap space(堆内存溢出)

Java heap space 是最常见的 OOM 问题,一般要么是发生内存泄漏,要么是堆内存实在放不下,参考上面的异常情况。
报错信息
解决方案:
  • 检查代码是否存在内存泄漏问题或者有超大对象。
  • 增加堆内存大小, -Xmx2g -Xms2g

4.2 GC overhead limit exceeded(GC 时间太长)

Java 程序超过 98% 的时间用来做 GC 并且回收不到 2% 的堆内存,且该动作连续重复了 5 次,就会抛出 java.lang.OutOfMemoryError:GC overhead limit exceeded 错误。简单来说,当前已经没有可用内存,经过多次 GC 之后仍然没能有效释放内存。排查思路和 Java heap space 相同。
报错信息

4.3 Metaspace(元空间溢出)

 Metaspace 是 Java 8 以后的方法区实现,主要存储的就是 JVM 加载到内存中的类相关数据。这个问题一般就是加载的类太多。
报错信息:
解决方案:
  • 增大元空间内存限制, -XX:MaxMetaspaceSize=512m
  • 直接去掉 -XX:MaxMetaspaceSize 启动参数,不限制 Metaspace 内存的大小。这种方式需要注意,假若机器物理内存不足,有可能会引起内存交换(swapping),严重拖累系统性能,还可能造成 native 内存分配失败等问题。

4.4 java.lang. StackOverflowError(栈内存溢出)

一般就是栈调用太深了,比如写了超长的递归。默认的线程栈大小跟操作系统和JDK 版本都有关系,默认范围为512 - 1024k。
报错信息
解决方案:
  • 检查代码是否有深度递归的问题
  • 检查栈内存大小参数是否设置过低,例如 -Xss256k

4.5 unable to create new native thread(无法创建线程)

高并发请求服务器时,经常出现如下异常:java.Lang.OutOfMemoryError: unable to create new native thread
准确的讲 native thread 异常与对应的平台有关,根本原因:服务器不允许应用程序创建这么多线程,例如 Linux 系统默认允许单个进程可以创建的线程数是 1024,MacOS 则是 4072。
报错信息
解决方案:
  • 降低你应用程序创建线程的数量,分析应用是否真的需要创建这么多线程。如果不是,改代码将线程数量降到最低。
  • 如果确实需要创建很多线程,超过 Linux 系统的默认 1024 个线程的限制,可以修改 Linux 服务器参数。

    4.6 Directbuffer memory buffer(直接内存溢出)

    使用 NIO 的时候经常使 ByteBuffer 读取或者写入数据,原理是使用 Native 函数库直接分配直接内存,然后通过一个存储在 Java 堆里面的 DirectByteBuffer 对象作为这块内存的引用进行操作,这样可以避免在 Java 堆和 Native 堆中来回复制数据,提高 IO 性能。
    直接内存不能通过 GC 进行回收,如果使用直接内存的时候没有及时释放,就可能会出现直接内存溢出的问题。
    报错信息:
    解决方案:
    • 确认直接内存被及时释放。
    • 增加最大直接内存限制,-XX:MaxDirectMemorySize=256m,默认等于 -Xmx

    5、GC 日志分析

    通过我们会开启 GC 日志打印,方便排查 GC 问题。增加 JVM 参数:
    Young GC日志分析
    notion image
    Full GC日志分析
    notion image

    6、常用工具

    6.1 命令行终端

    • 标准终端类:jps、jinfo、jstat、jstack、jmap
    • 功能整合类:jcmd、vjtools、arthas、greys

    6.2 可视化界面

    • 简易:JConsole、JVisualvm、HA、GCHisto、GCViewer
    • 进阶:MAT、JProfiler
    命令行推荐 arthas ,可视化界面推荐 JProfiler,MAT 教程参考

    6.3 在线工具

    • jstack:一款在线分析线程堆栈的工具,可以上传 jstack 得到的线程堆栈文件。
    • fastthread:同上,可以和 jstack 混合使用。
    • heaphero:一款在线的简单易用的内存分析工具,无需登录在线生成分析报告,可以上传 jmap 得到的内存快照文件。
    • gceasy:一款在线的 GC 日志分析器,可以通过 GC 日志分析进行内存泄露检测、GC 暂停原因分析、JVM 配置建议优化等功能。使用前需要添加 JVM 参数 -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -Xloggc:/tmp/gc.log,然后就可以上传 gc.log 文件。
    JVM系列:SafePoint与Stop The World详解线上问题排查:CPU突然飙升如何处理
    mcbilla
    mcbilla
    一个普通的干饭人🍚
    Announcement
    type
    status
    date
    slug
    summary
    tags
    category
    icon
    password
    🎉欢迎来到飙戈的博客🎉
    -- 感谢您的支持 ---
    👏欢迎学习交流👏