记录一次生产环境服务意外中止问题分析

发布时间:2023年05月23日 阅读:1981 次

1.前言

记录一次服务端服务意外停止

生产环境服务器总是经常意外停止,无任何日志,分析内存回收没有发现问题,java程序不存在内存泄漏既GC正常,FULLGC次数不多 GC日志正常 java程序无OOM

2.解决思路

1.分析内存泄漏 线程死锁 等

使用工具arthas

image-20230523103410311.png

无异常

因为经常在凌晨挂 1点左右 考虑是定时任务导致的 然后等到凌晨

image-20230523103534859.png

也无异常 这几次等待程序并未挂掉

分析日志 根本没有任何oom日志 凌晨挂掉的时候日志也没有任何异常日志

而且最大的一个原因是每次挂掉仿佛就是随机

甚至怀疑是是不是又脚本啥这个端口把文件都搜了一遍 如下图 也没有

image-20230523110025197.png

排查问题只能告一段落

3.意外收获

昨天下午19点左右的时候程序又出现服务挂掉

今天准备把java程序启动时加jvm参数的时候 想应该配置xmx多少合适呢 搜索服务器的java程序有很多个(大概7/8 个) 然后服务器内存16G  想一下每个分多少的时候突然想到 有没有是因为服务器内存不足把进程杀掉的呢

搜了一下 linux有什么机制会杀死进程回答如下

Linux中有多种机制可以杀死进程,其中一些常见的机制包括:

1. 用户手动杀死进程:用户可以使用kill命令或者killall命令手动杀死进程。

2. 内核自动杀死进程:当系统资源不足时,内核会自动杀死一些进程来释放资源。这个机制被称为OOM Killer。

3. 系统管理员杀死进程:系统管理员可以使用kill命令或者其他工具杀死进程。

4. 进程自己退出:进程可以自己退出,例如在完成任务后调用exit()函数。

5. 信号杀死进程:进程可以接收到信号,其中一些信号可以导致进程退出,例如SIGKILL和SIGTERM信号。

总之,Linux中有多种机制可以杀死进程,每种机制都有不同的使用场景和限制。

看来2可能是服务挂掉的原因

4.定位问题

搜了OOM Killer

OOM Killer是Linux内核中的一个机制,用于在系统内存不足时杀死进程以释放内存。当系统内存不足时,OOM Killer会选择一个进程并杀死它,以释放内存。

然后搜索跟OOM Killer相关的博客

相关链接

# OOM Killer官方文档
https://www.kernel.org/doc/gorman/html/understand/understand016.html
# OOM Killer的文章
https://blog.csdn.net/weixin_48101150/article/details/117220082
https://www.cnblogs.com/yaohuimo/p/11989916.html

https://segmentfault.com/a/1190000008268803
https://www.cnblogs.com/xibuhaohao/p/11087922.html
http://evertrain.blogspot.com/2018/04/oom.html
# overcommit_memory的文章
https://www.modb.pro/db/25980

大概意义就是: 当服务去申请内存时,服务器内存不足的时候会杀死一个进程释放内存

4.1.OOM Killer如何触发

引用博客中的

现代内核通常在分配内存时,允许申请的内存量超过实际可分配的free内存,这种技术称为Overcommit,可以认为是一种内存超卖的策略。开启了Overcommit,其实是基于一个普遍的规律——大部分应用并不会将其申请的内存全部用满——来尽量提升内存的利用率,可以允许系统分配出的内存总和超过系统能提供之和(物理内存+Swap空间),但是这种做法也导致OOM的风险。

overcommit的策略。

通过内核参数vm.overcommit_memory来进行控制,取值如下

0 表示内核将检查是否有足够的可用内存供应用进程使用;如果有足够的可用内存,内存申请允许;否则,内存申请失败,并把错误返回给应用进程。

Overcommit的内存过大将会失败,轻微的Overcommit将被允许。(如何区分?)

1 永远允许Overcommit,这种策略适合那些不能承受内存分配失败的应用,比如某些科学计算应用。

2 永远禁止Overcommit,在这个情况下,系统所能分配的内存不会超过swap+RAM*系数(/proc/sys/vm/overcmmit_ratio,默认50%,你可以调整),如果这么多资源已经用光,那么后面任何尝试申请内存的行为都会返回错误,这通常意味着此时没法运行任何新程序。

查看本机上的overcommit

grep -i commit /proc/meminfo

4.2.OOM Killer如何选择杀掉的进程

当物理内存和交换空间都被用完时,如果还有进程来申请内存,内核将触发OOM killer,其行为如下:

  1. 检查文件/proc/sys/vm/panic_on_oom,如果里面的值为2,那么系统一定会触发panic

  2. 如果/proc/sys/vm/panic_on_oom的值为1,那么系统有可能触发panic

  3. 如果/proc/sys/vm/panic_on_oom的值为0,或者上一步没有触发panic,那么内核继续检查文件/proc/sys/vm/oom_kill_allocating_task

  4. 如果/proc/sys/vm/oom_kill_allocating_task为1,那么内核将kill掉当前申请内存的进程

  5. 如果/proc/sys/vm/oom_kill_allocating_task为0,内核将检查每个进程的分数,分数最高的进程将被kill掉

查看我们服务器

cat /proc/sys/vm/panic_on_oom
cat /proc/sys/vm/oom_kill_allocating_task

image-20230523112941084.png

可以看到panic_on_oom和oom_kill_allocating_task都是0 既: 内核将检查每个进程的分数,分数最高的进程将被kill掉

4.2.1.找出最有可能被杀掉的进程

如何查询进程的分数

cat /proc/<pid>/oom_score

<pid>需要改掉

干脆写个脚本

vi oomscore.sh

#!/bin/bash
for proc in $(find /proc -maxdepth 1 -regex '/proc/[0-9]+'); do
   printf "%2d %5d %s\n" \
       "$(cat $proc/oom_score)" \
       "$(basename $proc)" \
       "$(cat $proc/cmdline | tr '\0' ' ' | head -c 50)"
done 2>/dev/null | sort -nr | head -n 10

chmod +x oomscore.sh
./oomscore.sh

运行结果

image-20230523113609714.png

确实是经常挂掉的两个服务

4.2.2.杀掉进程的日志

OOM killer会将kill的信息记录到系统日志/var/log/messages,检索相关信息就能匹配到是否进行了,如果配置了syslog,日志可能在/var/log/syslog里面。

查看日志

vim /var/log/messages

image-20230523113952415.png

日志太多 直接搜索 killer

image-20230523114024959.png

确实是昨天服务挂掉的时间

4.3. 避免的oom killer的方案

当然这些不适用 还是通过优化系统资源使用和调整进程优先级等方式来避免OOM Killer的触发

5.重现问题

既然找到问题是OOM Killer导致的服务挂掉 那我们来重现问题

环境 4H8G测试服务器 内核版本相同

思路

  1. 启动N个服务器 每个服务xms直接2G

  2. 每次启动查看oomscore

  3. 查看OOM killer杀掉进程的日志分析是不是最高的杀掉

开始测试

创建6个目录 6个服务 每个服务启动参数-Xmx2867m -Xms2867m

image-20230523114623584.png

启动前free -m

image-20230523122548773.png

开始启动

1.启动第一个服务分析内存

image-20230523122954316.png

分3G的内存oom score一下就排到了第一

查看杀掉进程日志tail -f /var/log/messages

image-20230523123239391.png

没有kill进程

2.启动第二个服务

image-20230523123312269.png

日志没有kill进程

3.启动第三个服务

image-20230523123511933.png

依然没有达到Kill进程的要求

4.启动第四个和第五个服务

image-20230523124009733.png

此时服务1的oomscore最高

5.继续启动第6个服务

第六个服务器还没启动第一个服务已经被杀死了

image-20230523124211928.png

此时的日志

image-20230523124229166.png

May 23 12:41:21 localhost kernel: Out of memory: Kill process 12172 (java) score 167 or sacrifice child May 23 12:41:21 localhost kernel: Killed process 12172 (java), UID 0, total-vm:6719316kB, anon-rss:863892kB, file-rss:0kB, shmem-rss:0kB May 23 12:41:21 localhost root: [euid=root]:root pts/0 2023-05-23 10:08 (192.168.0.36):[/data/app/demon/test1]2023-05-23 12:37:42 root java -Xmx3867m -Xms2867m -jar content-server-1.jar --server.port=9001

由此可见问题重现

6.解决问题

  1. 每个服务限制Xmx 使得总服务的Xmx之和小于服务器内存 内存不足交由GC回收而不是申请新的内存

  2. 关闭OOM Killer(不推荐) 如果关闭虽然不会杀死之前的服务 但是新的服务无法启动

相关JVM参数

#2G参数 -Xmx1433m -Xms1433m -XX:MaxMetaspaceSize=204m -XX:MetaspaceSize=204m -XX:-ExplicitGCInvokesConcurrentAndUnloadsClasses -XX:MaxDirectMemorySize=204m -XX:+UseG1GC -XX:MaxGCPauseMillis=100 #4G参数 -Xmx2867m -Xms2867m -XX:MaxMetaspaceSize=409m -XX:MetaspaceSize=409m -XX:-ExplicitGCInvokesConcurrentAndUnloadsClasses -XX:MaxDirectMemorySize=409m -XX:+UseG1GC -XX:MaxGCPauseMillis=100 #8G参数 -Xmx5734m -Xms5734m -XX:MaxMetaspaceSize=512m -XX:MetaspaceSize=512m -XX:-ExplicitGCInvokesConcurrentAndUnloadsClasses -XX:MaxDirectMemorySize=819m -XX:+UseG1GC -XX:MaxGCPauseMillis=100

参数解释

-Xmx  #最大堆大小 -Xms  #初始堆大小 Xms跟Xmx设置一样的值可以防止内存抖动 jvm向操作系统申请内存时都需要一定的开销 #关于内存抖动文档 https://blog.csdn.net/javadada1197/article/details/119414239 -XX:MaxMetaspaceSize #最大元空间 -XX:MetaspaceSize #元空间 -XX:-ExplicitGCInvokesConcurrentAndUnloadsClasses #防止出现意料之外的”stop-the-world”的系统GC -XX:MaxDirectMemorySize #此参数的含义是当Direct ByteBuffer分配的堆外内存到达指定大小后,即触发Full GC -XX:+UseG1GC #使用G1回收器 Garbage First -XX:MaxGCPauseMillis #默认也是100ms 表示每次GC最大的停顿毫秒数,VM将调整Java堆大小和其他与GC相关的参数,以使GC引起的暂停时间短于nnn毫秒,尽可能地保证内存回收花费时间不超过设定值 该参数应谨慎使用。太小的值将导致系统花费过多的时间进行垃圾回收。原因是为满足最大暂停时间,VM将设置更小的堆,以存储相对少量的对象,来提升回收速率,会导致更高频率的GC


Tag:
相关文章