记录一次服务端服务意外停止
生产环境服务器总是经常意外停止,无任何日志,分析内存回收没有发现问题,java程序不存在内存泄漏既GC正常,FULLGC次数不多 GC日志正常 java程序无OOM
2.解决思路
1.分析内存泄漏 线程死锁 等
使用工具arthas
无异常
因为经常在凌晨挂 1点左右 考虑是定时任务导致的 然后等到凌晨
也无异常 这几次等待程序并未挂掉
分析日志 根本没有任何oom日志 凌晨挂掉的时候日志也没有任何异常日志
而且最大的一个原因是每次挂掉仿佛就是随机
甚至怀疑是是不是又脚本啥这个端口把文件都搜了一遍 如下图 也没有
排查问题只能告一段落
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,其行为如下:
检查文件/proc/sys/vm/panic_on_oom,如果里面的值为2,那么系统一定会触发panic
如果/proc/sys/vm/panic_on_oom的值为1,那么系统有可能触发panic
如果/proc/sys/vm/panic_on_oom的值为0,或者上一步没有触发panic,那么内核继续检查文件/proc/sys/vm/oom_kill_allocating_task
如果/proc/sys/vm/oom_kill_allocating_task为1,那么内核将kill掉当前申请内存的进程
如果/proc/sys/vm/oom_kill_allocating_task为0,内核将检查每个进程的分数,分数最高的进程将被kill掉
查看我们服务器
cat /proc/sys/vm/panic_on_oom
cat /proc/sys/vm/oom_kill_allocating_task
可以看到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
运行结果
确实是经常挂掉的两个服务
4.2.2.杀掉进程的日志
OOM killer会将kill的信息记录到系统日志/var/log/messages,检索相关信息就能匹配到是否进行了,如果配置了syslog,日志可能在/var/log/syslog里面。
查看日志
vim /var/log/messages
日志太多 直接搜索 killer
确实是昨天服务挂掉的时间
4.3. 避免的oom killer的方案
直接修改/proc/PID/oom_adj文件,将其置位-17
修改/proc/sys/vm/lowmem_reserve_ratio
直接关闭oom-killer
当然这些不适用 还是通过优化系统资源使用和调整进程优先级等方式来避免OOM Killer的触发
5.重现问题
既然找到问题是OOM Killer导致的服务挂掉 那我们来重现问题
环境 4H8G测试服务器 内核版本相同
思路
启动N个服务器 每个服务xms直接2G
每次启动查看oomscore
查看OOM killer杀掉进程的日志分析是不是最高的杀掉
开始测试
创建6个目录 6个服务 每个服务启动参数-Xmx2867m -Xms2867m
启动前free -m
开始启动
1.启动第一个服务分析内存
分3G的内存oom score一下就排到了第一
查看杀掉进程日志tail -f /var/log/messages
没有kill进程
2.启动第二个服务
日志没有kill进程
3.启动第三个服务
依然没有达到Kill进程的要求
4.启动第四个和第五个服务
此时服务1的oomscore最高
5.继续启动第6个服务
第六个服务器还没启动第一个服务已经被杀死了
此时的日志
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.解决问题
每个服务限制Xmx 使得总服务的Xmx之和小于服务器内存 内存不足交由GC回收而不是申请新的内存
关闭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