遇到 Java 内存溢出 (OOM) 时,这样排查
由于UAT
环境,产品需要立马使用,所以我们需要先保证UAT
环境可用,所以我们做了以下操作(本项目是集群搭建)
先查看所有节点是否都是因为内存溢出的问题 使用
docker stats
发现所有节点的内存占用以及 cpu 占用都已经超负荷了使用
docker inspect CONTAINER_ID
,可以直接看到当前实例运行的是什么项目程序我们打算先重启部分当前项目的节点,先保证
UAT
环境暂时可用重启后,再次使用
docker stats
发现重启后的节点 内存还是在60%
左右浮动,此时访问当前项目发现是可以访问了。
因为UAT
暂时可用使用了,所以接下来就是我们自己的排查时间了。但是因为重启后的节点 内存还是居高不下,所以我们也需要迅速排查出来问题。否则已经重启的节点还是随时可能内存溢出
排查原因
上述 修复措施
时我们已经找到了 项目中的 CONTAINER_ID
,所以我们直接进入 实例内部具体看看是因为什么 导致的内存和 CPU 超负荷的问题。
docker inspect CONTAINER_ID //查看当前 docker 实例的详细信息,可以直接看出当前实例是什么项目
docker exec -it CONTAINER_ID /bin/bash //进入 docker 实例
top //查看当前实例内进程情况
top -Hp PID // 找到内存和 CPU 占用率最高的 PID 查看当前进程的所有线程占用情况
printf "%x\n" Thead_PID // 把线程 PID 转换成 16 进制 ,因为我们在做堆栈排查时需要使用到 16 进制的线程 PID
到这里我们基本上前期的准备都做好了,接下来我们就应该去排查程序内到底时因为什么线程,或者那些对象占用了这么高的内存和 CPU
因为我们需要排查Java
程序堆栈信息,所以我们必须要用到当前 Java 程序所运行的JDK
自带的排查命令。但是这里很可惜,由于我们项目使用的基础镜像是阉割版的 JDK
我在 jdk/bin
下面没有找到相关命令。由于重新使用完整版基础镜像启动实例,实例本身就会被重启,如果重启了那可能还需要等到下一次发生该问题的时候我们才能排查,所以我们做了另一个决定,找一个当前相同版本的JDK
将 当前的 Java
环境更改为这个完整版的JDK
的环境,但是我们并没有操作成功,在执行相关排查命令的时候一直提示我 当前环境和本身Java
程序环境不匹配。可能是因为中间出现了什么问题,这个就没有去深究下去了。所以我们还是选择了,更换镜像后重启当前实例节点。
一天后,我们发现UAT
再次无法访问,我们通过上述的操作定位到有问题的 docker 实例,排查过程如下
jstack -l PID > jastck_PID.log // 使用 jstack 可以看到当前程序中所有线程信息
我们使用上述中 将线程 ID 转换成 16 进制后的 标识,作为搜索条件
grep 16 进制的线程 ID -A20 -B20 jastck_PID.log
这样我们就能看到到底是哪个线程出现了 占用 CPU 或者内存超载的问题,从而找到对应的业务代码。但是很可惜,我们发现我们这个线程是程序在执行 GC 操作时所产生的线程。所以很明显这是一个由于内存溢出,而导致 Java 程序一直执行 GC 操作,引发的 CPU 超负荷的问题。于是我们继续排查当前项目所占用的内存情况
由于我们在上述排查时发现 CPU 超负荷的问题是在于 GC 线程,我们首先先使用查看当前项目 GC 执行次数的命令,确定当前 GC 执行情况
jstat -gcutil PID 2000 10 //每两秒取样一次当前进程的内存情况
我们执行当前命令会看到当前进程中的内存占用情况,下面是每一列的官方解释:
S0: Survivor space 0 utilization as a percentage of the space's current capacity.
S1: Survivor space 1 utilization as a percentage o
f the space's current capacity.
E: Eden space utilization as a percentage of the space's current capacity.
O: Old space utilization as a percentage of the space's current capacity.
P: Permanent space utilization as a percentage of the space's current capacity.
YGC: Number of young generation GC events.
YGCT: Young generation garbage collection time.
FGC: Number of full GC events.
FGCT: Full garbage collection time.
GCT: Total garbage collection time.
我们发现我们的年轻代
,老年代
,永久代
的内存占用情况基本上都是百分百了,同时在取样的过程中发现 GC 的执行时间一直在增加,但是 GC 次数没有增加,说明我们进程在执行 GC 时,有无法回收的对象,导致进程在一直 GC
jmap,为了获取的数据准确,可能会挂起线程,会导致应用暂停
评论