java tomcat服务无缘无故挂掉分析和解决方案

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介: 最近有同事反应有时候xxx系统有时候会时不时出现服务异常提示,一上机器,发现xxx服务进程不在,重启服务后又恢复了,所以这边就需要去跟进问题。

背景


最近有同事反应有时候xxx系统有时候会时不时出现服务异常提示,一上机器,发现xxx服务进程不在,重启服务后又恢复了,所以这边就需要去跟进问题。

问题定位


java tomcat服务挂掉原因,主要怀疑方向有这几个:

1.服务器被人重启,导致服务没有起来

2.错误异常导致程序挂掉

3.服务器占用内存过高,Linux强制退出程序

4.其他原因 下面就开始逐一排查

服务器重启


如何查看服务器是否被重启,主要依据下面的命令:

who -b查看最后一次重启时间

8c87bcf50f28f4b0e47a9f8c96a25bb.pnglast reboot 查看服务器历史重启

cfaaa75c9f990e1d92899bd0afb8744.png

发现服务器重启时间是几个月前的事,因此可以排除。

错误异常导致程序挂掉


java服务采用spring log分级日志,直接看对应时间点日志,并没有发现什么,因此可以排除掉。

服务器占用内存过高,Linux强制退出程序


如何查看服务器系统日志,可以查看文件:/var/log/messages

message日志包含了系统启动时的引导消息,以及系统运行时的其他状态消息。IO 错误、网络错误和其他系统错误都会记录到这个文件中。

如何排查呢?执行以下命令:cat /var/log/messages | grep java然后发现有下面日志:

Out of memory: Kill process 9682 (java) score 9 or sacrifice child

因此判断由于内存占用过高,java服务被系统误杀了。 既然定位到问题根源,那么为了更好的解决问题,我们继续追踪问题,为什么系统会kill java服务,而不杀掉其他进程呢?这里就需要了解一下Linux Out of Memory (OOM) killer机制。

Linux OOM机制


是什么


Linux内核设计的一种机制,在内存不足的时候,会选择一个占用内存较大的进程并kill掉这个进程,以满足系统内存申请需求。

触发机制


触发条件:内存不足,为什么会出现内存不足,这里涉及到Linux内存结构和使用机制:

1.物理内存结构

2.overcommit机制

3.OOM killer机制

Linux内存结构


这里就简单讲一下,具体描述可以google一下Linux物理内存结构。 Linux物理内存结构,Linux内核会把物理内存按照node(节点) > zone(分区)> page (内存页)三级结构进行划分,俗称内存管理系统,然后CPU会根据这种内存管理系统去调用内存。简单介绍以下概念:

1.node节点:每个CPU都有自己的node内存节点,可以多个也可以单个,单个叫UMA架构,多个叫NUMA架构

2.zone分区:每个Node划分很多zone,每个zone都有自己的功能定义,这种只是从软件层面划分定义。zone里还有一个概念叫分配价值链

  • 分配价值链: 普通的内存分配会有一个“价值”的层次结构

3.page内存页:属于zone下面的内存页,每个页基础大小是4K,他们维护在一个叫free_area的数组结构中 下面是从网上找的Linux物理内存结构图:

fc257d21a2d188d69ddee22fec3d827.png

虚拟内存(swap空间)


相对于物理内存,在 Linux 下还有一个虚拟内存的概念,虚拟内存是为了满足物理内存的不足而提出的策略,它是利用磁盘空间虚拟出的一块逻辑内存。用作虚拟内存的磁盘空间被称为交换空间(又称 swap 空间)。


了解Linux物理内存结构,我们明白Linux 的内存管理采取的是分页存取机制,为了保证物理内存能得到充分的利用,内核会在适当的时候将物理内存中不经常使用的数据块自动交换到虚拟内存中,而将经常使用的信息保留到物理内存。


Linux 内存运行机制:

  • Linux 系统会不时地进行页面交换操作,以保持尽可能多的空闲物理内存,即使并没有什么事情需要内存,Linux 也会交换出暂时不用的内存页面,因为这样可以大大节省等待交换所需的时间
  • Linux 进行页面交换是有条件的,不是所有页面在不用时都交换到虚拟内存,Linux 内核根据“最近最经常使用”算法,仅仅将一些不经常使用的页面文件交换到虚拟内存

虚拟内存是允许设置大小,这也是解决OMM killer的一种解决方案,具体可以看后面的解决方案。

overcommit机制


有了虚拟内存的存在,那么进程就可以向系统申请比物理剩余内存更大的使用内存:

在实际申请内存的时候,比如申请1G,并不会在物理区域中分配1G的真实物理内存,而是分配1G的虚拟内存,等到需要的时候才去真正申请物理内存,也就是说申请不等于分配

这就是overcommit机制,允许进程申请比物理内存实际大的内存。但是这会面临一个问题,当进程真正需要这么多内存怎么办,Linux的解决方案就是OOM killer。

当然,overcommit也允许设置几种值(vm.overcommit_memory):

  • 0 – Heuristic overcommit handling. 这是缺省值,它允许overcommit,但过于明目张胆的overcommit会被拒绝,比如malloc一次性申请的内存大小就超过了系统总内存
  • 1 – Always overcommit. 允许overcommit,对内存申请来者不拒。
  • 2 – Don’t overcommit. 禁止overcommit。

OOM killer机制


讲完overcommit,终于来到本文重点,OOM killer机制,这应该是很多Linux系统部署服务,开发者所要面临头疼地方。 OOM killer,全称 Out Of Memory Killer,俗称内存溢出杀手。它是如何执行的呢?

OMM killer机制:linux会为每个进程算一个分数,最终他会将分数最高的进程kill

有三个进程设置值可以影响到分数值,可手动设置,但是基本上都不会用上,仅用来了解或者临时解决方案:

  • /proc/<pid>/oom_score_adj, 取值范围为-1000到1000, 如果将该值设置为-1000,则进程永远不会被杀死,因为此时 badness score 永远返回0
  • /proc/<pid>/oom_adj, 取值是-17到+15,取值越高,越容易被干掉。如果是-17,则表示不能被kill
  • /proc/<pid>/oom_score, 是系统综合进程的内存消耗量、CPU时间(utime + stime)、存活时间(uptime - start time)和oom_adj计算出的,消耗内存越多分越高。

除了这三个值,还有一种计算方式:子进程内存:Linux在计算进程的内存消耗的时候,会将子进程所耗内存的一半同时算到父进程中。这样,那些子进程比较多的进程就要小心了。

如何确定进程是被OOM killer干掉的


java tomcat查看之前的进程id或进程名,可以通过命令ps -ef | grep java获取到。 其次,查找系统日志grep "Out of memory" /var/log/messages,对比一下进程id或进程名,进行判断。

f9412da2753a7317391c67cdad4cc0f.png

解决方案


关闭OOM机制(不推荐,可作为临时解决方案)


执行以下命令:

sysctl -w vm.overcommit_memory=2

或者

echo "vm.overcommit_memory=2" >> /etc/sysctl.conf

或者修改进程oom_score_adj值:

sudo echo -1000 > /proc/$pid/oom_score_adj

或者修改进程oom_adj值:

  • /proc/PID/oom_adj文件,将其置位-17

设置java进程最大占用内存(推荐)


java tomcat服务在启用进程的时候可以设置占用最大内存,具体数值可以参考当前服务器所剩余的内存设置,具体设置如下:

java -Xms512m -Xmx512m -jar xxx.jar
  • Xms: 最小内存
  • Xmx: 最大内存 tomcat可以在TOMCAT_HOME/bin/catalina.sh中设置:
# 在cygwin=false前
JAVA_OPTS="-server -Xms256m -Xmx512m -XX:PermSize=64M -XX:MaxPermSize=128m"

java守护进程(推荐)


除了设置最大占用内存设置,还可以增加守护进程从而避免服务异常挂掉进行重启,主要有两种方案:

  1. 第一种常用,通过设置crontab脚本去守护。
  2. 第二种是Java jsvc方案,利用启动守护进程去监控控制服务进程,从而避免进程无缘无故挂掉自动重启,tomcat本身已有daemon.sh,可以直接该脚本即可。

优化代码(有能力者可以采用)


这个可能需要具体问题具体分析了,优化代码占用内存,java网上有很多方案,大家各自采纳符合自己的方案即可。

申请更多内存(土豪随意)


既然是内存不够,那么就直接申请更多资源,就可以满足了,看来还是有钱就能更快解决问题。

相关实践学习
【涂鸦即艺术】基于云应用开发平台CAP部署AI实时生图绘板
【涂鸦即艺术】基于云应用开发平台CAP部署AI实时生图绘板
目录
相关文章
|
1月前
|
Java Go 开发工具
【Java】(9)抽象类、接口、内部的运用与作用分析,枚举类型的使用
抽象类必须使用abstract修饰符来修饰,抽象方法也必须使用abstract修饰符来修饰,抽象方法不能有方法体。抽象类不能被实例化,无法使用new关键字来调用抽象类的构造器创建抽象类的实例。抽象类可以包含成员变量、方法(普通方法和抽象方法都可以)、构造器、初始化块、内部类(接 口、枚举)5种成分。抽象类的构造器不能用于创建实例,主要是用于被其子类调用。抽象类中不一定包含抽象方法,但是有抽象方法的类必定是抽象类abstract static不能同时修饰一个方法。
184 1
|
1月前
|
存储 Java Go
【Java】(3)8种基本数据类型的分析、数据类型转换规则、转义字符的列举
牢记类型转换规则在脑海中将编译和运行两个阶段分开,这是两个不同的阶段,不要弄混!
168 2
|
1月前
|
JSON Java 数据格式
java调用服务报错400
java调用服务报错400
67 6
java调用服务报错400
|
1月前
|
JSON Java 数据格式
java调用服务报错415 Content type ‘application/octet-stream‘ not supported
java调用服务报错415 Content type ‘application/octet-stream‘ not supported
118 6
|
2月前
|
数据采集 存储 弹性计算
高并发Java爬虫的瓶颈分析与动态线程优化方案
高并发Java爬虫的瓶颈分析与动态线程优化方案
|
3月前
|
安全 Java 编译器
new出来的对象,不一定在堆上?聊聊Java虚拟机的优化技术:逃逸分析
逃逸分析是一种静态程序分析技术,用于判断对象的可见性与生命周期。它帮助即时编译器优化内存使用、降低同步开销。根据对象是否逃逸出方法或线程,分析结果分为未逃逸、方法逃逸和线程逃逸三种。基于分析结果,编译器可进行同步锁消除、标量替换和栈上分配等优化,从而提升程序性能。尽管逃逸分析计算复杂度较高,但其在热点代码中的应用为Java虚拟机带来了显著的优化效果。
100 4
|
3月前
|
存储 数据采集 搜索推荐
Java 大视界 -- Java 大数据在智慧文旅旅游景区游客情感分析与服务改进中的应用实践(226)
本篇文章探讨了 Java 大数据在智慧文旅景区中的创新应用,重点分析了如何通过数据采集、情感分析与可视化等技术,挖掘游客情感需求,进而优化景区服务。文章结合实际案例,展示了 Java 在数据处理与智能推荐等方面的强大能力,为文旅行业的智慧化升级提供了可行路径。
Java 大视界 -- Java 大数据在智慧文旅旅游景区游客情感分析与服务改进中的应用实践(226)
|
3月前
|
机器学习/深度学习 安全 Java
Java 大视界 -- Java 大数据在智能金融反洗钱监测与交易异常分析中的应用(224)
本文探讨 Java 大数据在智能金融反洗钱监测与交易异常分析中的应用,介绍其在数据处理、机器学习建模、实战案例及安全隐私等方面的技术方案与挑战,展现 Java 在金融风控中的强大能力。
|
4月前
|
存储 Java 大数据
Java 大视界 -- Java 大数据在智能家居能源消耗模式分析与节能策略制定中的应用(198)
简介:本文探讨Java大数据技术在智能家居能源消耗分析与节能策略中的应用。通过数据采集、存储与智能分析,构建能耗模型,挖掘用电模式,制定设备调度策略,实现节能目标。结合实际案例,展示Java大数据在智能家居节能中的关键作用。
|
4月前
|
分布式计算 搜索推荐 算法
Java 大视界 -- Java 大数据在智慧养老服务需求分析与个性化服务匹配中的应用(186)
本篇文章探讨了Java大数据技术在智慧养老服务需求分析与个性化服务匹配中的应用。通过整合老年人健康数据与行为数据,结合机器学习与推荐算法,实现对老年人健康风险的预测及个性化服务推荐,提升养老服务的智能化与精准化水平,助力智慧养老高质量发展。