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网上有很多方案,大家各自采纳符合自己的方案即可。

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


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

相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
目录
相关文章
|
1月前
|
存储 Java
【编程基础知识】 分析学生成绩:用Java二维数组存储与输出
本文介绍如何使用Java二维数组存储和处理多个学生的各科成绩,包括成绩的输入、存储及格式化输出,适合初学者实践Java基础知识。
71 1
|
19天前
|
存储 Java 关系型数据库
在Java开发中,数据库连接是应用与数据交互的关键环节。本文通过案例分析,深入探讨Java连接池的原理与最佳实践
在Java开发中,数据库连接是应用与数据交互的关键环节。本文通过案例分析,深入探讨Java连接池的原理与最佳实践,包括连接创建、分配、复用和释放等操作,并通过电商应用实例展示了如何选择合适的连接池库(如HikariCP)和配置参数,实现高效、稳定的数据库连接管理。
37 2
|
20天前
|
Java 关系型数据库 数据库
面向对象设计原则在Java中的实现与案例分析
【10月更文挑战第25天】本文通过Java语言的具体实现和案例分析,详细介绍了面向对象设计的五大核心原则:单一职责原则、开闭原则、里氏替换原则、接口隔离原则和依赖倒置原则。这些原则帮助开发者构建更加灵活、可维护和可扩展的系统,不仅适用于Java,也适用于其他面向对象编程语言。
13 2
|
1月前
|
Java
让星星⭐月亮告诉你,Java synchronized(*.class) synchronized 方法 synchronized(this)分析
本文通过Java代码示例,介绍了`synchronized`关键字在类和实例方法上的使用。总结了三种情况:1) 类级别的锁,多个实例对象在同一时刻只能有一个获取锁;2) 实例方法级别的锁,多个实例对象可以同时执行;3) 同一实例对象的多个线程,同一时刻只能有一个线程执行同步方法。
18 1
|
1月前
|
小程序 Oracle Java
JVM知识体系学习一:JVM了解基础、java编译后class文件的类结构详解,class分析工具 javap 和 jclasslib 的使用
这篇文章是关于JVM基础知识的介绍,包括JVM的跨平台和跨语言特性、Class文件格式的详细解析,以及如何使用javap和jclasslib工具来分析Class文件。
41 0
JVM知识体系学习一:JVM了解基础、java编译后class文件的类结构详解,class分析工具 javap 和 jclasslib 的使用
|
1月前
|
Java
如何从Java字节码角度分析问题|8月更文挑战
如何从Java字节码角度分析问题|8月更文挑战
|
24天前
|
存储 Java 编译器
[Java]基本数据类型与引用类型赋值的底层分析
本文详细分析了Java中不同类型引用的存储方式,包括int、Integer、int[]、Integer[]等,并探讨了byte与其他类型间的转换及String的相关特性。文章通过多个示例解释了引用和对象的存储位置,以及字符串常量池的使用。此外,还对比了String和StringBuilder的性能差异,帮助读者深入理解Java内存管理机制。
18 0
|
1月前
|
Java 数据库
基于java的汽车服务管理系统(Car Service Management System)
基于java的汽车服务管理系统(Car Service Management System)
19 0
|
5月前
|
Java 应用服务中间件 Apache
安装和配置Apache Tomcat是部署Java Web应用程序的常见任务
安装和配置Apache Tomcat是部署Java Web应用程序的常见任务
144 7