异常日志

Out Of Memory: Kill process

JVM OOM监控加服务重启 Linux脚本_oom


解决方法:

通过配置JVM参数做内存快照,--XX:+HeapDumpOnOutOfMemoryError这个参数的含义是:当堆内存溢出时触发输出内存快照。通常搭配--XX:HeapDumpPath参数,指定快照的输出路径;同时可以指定快照文件名,默认快照文件名是 java_<pid>_<date>_<time>_heapdump.hprof。可以通过-XX:OnOutOfMemoryError参数指定发生内存溢出后执行的脚本。

--XX:+HeapDumpOnOutOfMemoryError

--XX:HeapDumpPath=/usr/local/hprof/

-XX:OnOutOfMemoryError='sh /usr/shellfile/xxxxx.sh'

注意:--XX:+HeapDumpOnOutOfMemoryError 触发条件是 java.lang.OutOfMemoryError: Java heap space。所以System.gc() 和fullggc并不会触发内存溢出快照,所以再写调试代码时,不要使用这两种方法。


配置tomcat的bin目录下的setenvsh文件

JVM OOM监控加服务重启 Linux脚本_java_02

为了处理OOM后,通过-XX:OnOutOfMemoryError指定溢出后执行的脚本,处理服务异常。我最开始的计划是写一个restart的脚本,内存溢出后直接重启服务,短时间内当前节点可恢复运行,当然这是建立在有阿里云SLB心跳检查的基础上。

在写好脚本后,调试时发现无论我在一个脚本直接shutdown后kill 再重启,还是通过setsid和nohup …… & 命令做重启,都会出现一些进程把自己杀死,或者tomcat端口没有及时释放导致重启中端口占用的情况。所以最后决定 采用内存溢出后将tomcat shutdown并kill,通过linux crontab 做监控,发现访问异常,执行重启。

我们采用的 tomcat war包部署,所以使用tomcat的startup.sh 和 shutdown.sh

oom_exception.sh

#!/bin/sh
path='/usr/local/tomcatTest/'
shellfile=$path'bin/kill_tomcat.sh'

echo ===========OOM 执行强制关闭tomcat脚本==============
echo $shellfile

setsid /usr/shellfile/kill_tomcat.sh

echo ===========OOM 执行强制关闭tomcat脚本=============


kill_tomcat.sh

#!/bin/sh
p='/usr/tomcatTest'
#p=$(cd $(dirname $0)./; pwd)
work=${p}'/work/'
`rm -rf ${work}`
tomcatpath=${p}'/bin'
echo 'operate restart tomcat: '$tomcatpath
pid=`ps aux | grep $tomcatpath | grep -v grep | grep -v retomcat | awk '{print $2}'`
echo 'exist pid:'$pid

#实时日志路径
curDate=$(date "+%Y-%m-%d")
realtimelog=${p}'/logs/catalina.'${curDate}'.out'
if [ ! -f "${realtimelog}" ]
then
realtimelog=${p}'/logs/catalina.out'
fi
echo ===========realtimelog==============
echo ${realtimelog}

if [ -n "$pid" ]
then
{
echo ===========shutdown================
$tomcatpath'/shutdown.sh'
sleep 2
pid=`ps aux | grep $tomcatpath | grep -v grep | grep -v retomcat | awk '{print $2}'`
if [ -n "$pid" ]
then
{
sleep 2
echo ========kill tomcat begin==============
echo $pid
kill -9 $pid
echo ========kill tomcat end==============
}
fi
}
else
echo ===========not found pid==============
fi

执行完kill_tomcat.sh脚本后,此时这个tomcat就彻底被杀掉了。

然后,通过tomcat监控的脚本,监控tomcat被杀掉后重新启动,使用linux crontab延时执行。并且使用了钉钉群消息的方式通知相关人员。

tomcatMonitor.sh

#!/bin/sh

# func:系统监控 服务器宕机后,提醒并重启

# DEFINE

# 定义变量
hostname=`hostname`
nowtime=`date "+%Y-%m-%d-%H:%M:%S"`
host=127.0.0.1:6901
hostip=`hostname -I`
hostdes="$hostname $hostip"

# 定义要监控的页面地URL
WebUrl=http://$host/DoNOTremove.txt

# 日志输出
GetPageInfo=/tmp/TomcatMonitor.Info
TomcatMonitorLog=/tmp/TomcatMonitor.log

# 定义消息的标题
subject="[$hostname]系统运行监控"
subject2="[$hostdes]系统"
body=""

function SendMessageToDingding(){
url="https://oapi.dingtalk.com/robot/send?access_token=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
UA="Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/535.24 (KHTML, like Gecko) Chrome/19.0.1055.1 Safari/535.24"
res=`curl -XPOST -s -L -H "Content-Type:application/json" -H "charset:utf-8" $url -d "
{ \"msgtype\":\"text\",
\"text\":{\"content\":\"$1\n$2\"},
\"at\":{
\"atMobiles\":[\"18888888888\"],
\"isAtAll\":false
}
}"`
echo $res
}

Monitor()
{
echo "$hostdes"
pidpath=/tmp/tcmonV1.pid #定义pid文件
if [ -f "$pidpath" ] ;then
echo "[$nowtime]脚本已经在执行,准备退出"
cat $pidpath
exit 2
else
echo $$ >$pidpath #将当前Shell进程号记录到pid文件中
echo "[$nowtime]脚本开始执行"
cat $pidpath

echo "[$nowtime][info]开始监测linux服务器..."

res=`curl -XPOST -m 10 -s -L -H "Content-Type:application/json" -H "charset:utf-8" $WebUrl `
echo $res
if [[ $res =~ "hello running" ]] ;then
echo "[$nowtime][success]tomcat运行正常,接口访问正常......"
else
echo "$nowtime][error]访问失败,系统准备自动重启"
body="[$nowtime][error]访问失败,系统准备自动重启"
SendMessageToDingding $subject $body

# 定义要重启的脚本路径
cd /usr/tomcatTest/bin/
./restartup.sh
sleep 5m

# 检查是否重启成功
res=`curl -m 30 -XPOST -s -L -H "Content-Type:application/json" -H "charset:utf-8" $WebUrl `
echo $res
if [[ $res =~ "hello running" ]] ;then
echo "[$nowtime][success]tomcat重启成功,接口访问正常......"
body="[$nowtime][success]tomcat重启成功,接口访问正常......"
SendMessageToDingding $subject $body
else
echo "[$nowtime][error]tomcat启动可能失败或者重启超时,请管理员检查"
body="[$nowtime][error]tomcat启动可能失败或者重启超时,请管理员检查"
SendMessageToDingding $subject $body
fi
fi
echo "[$nowtime][info]结束监测linux服务器..."
rm -f $pidpath
fi

}
Monitor>>$TomcatMonitorLog

测试直接执行脚本。( 如果执行新建脚本提示没有权限,可通过 chmod u+x *.sh ,为当前用户增加可执行权限)

JVM OOM监控加服务重启 Linux脚本_oom_03

脚本执行,已检测到tomcat无法访问,执行重启

在重启过程中如果找不到 JRE_HOME ,需要重新配置一下,在setenv.sh中。本文setenv.sh 文件中已添加。

将通过crontab -e命令 ,当前用户新建一个任务文件,或者 vim /etc/crontab 打开全局的任务文件配置。

加入以下配置,配置是指每分钟执行,每延时10秒执行一次。以下配置结果是每10秒执行一次监控脚本。系统每隔1分钟会读取任务列表,修改后手动重启的意义不大。

* * * * * /bin/sh /usr/local/tomcatMonitor.sh
* * * * * sleep 10; /bin/sh /usr/local/tomcatMonitor.sh
* * * * * sleep 20; /bin/sh /usr/local/tomcatMonitor.sh
* * * * * sleep 30; /bin/sh /usr/local/tomcatMonitor.sh
* * * * * sleep 40; /bin/sh /usr/local/tomcatMonitor.sh
* * * * * sleep 50; /bin/sh /usr/local/tomcatMonitor.sh

查看脚本执行日志

JVM OOM监控加服务重启 Linux脚本_java_04

可以看到脚本正常执行,日志打印也正常。接下来写一个会抛出OOM异常的接口,测试是否会是否后正常kill服务,监控脚本检测服务无法访问后重启tomcat。

@ResponseBody
@RequestMapping(value = "testHeap", method = RequestMethod.GET)
public Object testHeap() {

List<byte[]> list = new ArrayList<>();
Integer i = 0;
while(true){
list.add(new byte[10*1024*1024]);
System.out.println("内存分配次数:" + (++i));
}

}

经测试,监控状态正常。


原文链接:​​https://blog.csdn.net/lishuzhen5678/article/details/109719662​