概述
使用boot+dubbo框架开发的项目,在生产环境中正常运行两个月,在一天夜里发生宕机。
查看日志发现,JVM在凌晨1点钟做GC,尝试了多次,GC失败,直接抛出OOM异常。
异常日志
Exception: java.lang.OutOfMemoryError thrown from the UncaughtExceptionHandler in thread "http-nio2-8801-Acceptor-0"
Exception in thread "SimplePauseDetectorThread_1" java.lang.OutOfMemoryError: GC overhead limit exceeded
java.lang.OutOfMemoryError: GC overhead limit exceeded
Exception in thread "http-nio2-8801-Acceptor-0" java.lang.OutOfMemoryError: Java heap space
at java.nio.HeapByteBuffer.<init>(HeapByteBuffer.java:57)
02:35:02.282 [localhost-startStop-2-SendThread(iZwz9figezp50iy04h7ufyZ:2181)] WARN o.a.z.ClientCnxn - [run,1112] - Client session timed out, have not heard from server in 46501ms for sessionid 0x100113ff5af0052
at java.nio.ByteBuffer.allocate(ByteBuffer.java:335)
at org.apache.tomcat.util.net.SocketBufferHandler.<init>(SocketBufferHandler.java:41)
at org.apache.tomcat.util.net.Nio2Endpoint.setSocketOptions(Nio2Endpoint.java:312)
at org.apache.tomcat.util.net.Nio2Endpoint$Acceptor.run(Nio2Endpoint.java:418)
at java.lang.Thread.run(Thread.java:748)
02:35:03.959 [localhost-startStop-2-SendThread(iZwz9figezp50iy04h7ufyZ:2181)] INFO o.a.z.ClientCnxn - [run,1160] - Client session timed out, have not heard from server in 46501ms for sessionid 0x100113ff5af0052, closing socket connection and attempting reconnect
上线时没有做内存快照,根据日志排查也没有发现直接问题。所以先做快照并且加入tomcat监控脚本。
通过配置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文件
为了处理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 $shellfilesetsid /usr/shellfile/kill_tomcat.shecho ===========OOM 执行强制关闭tomcat脚本=============
kill_tomcat.sh
#!/bin/shp='/usr/tomcatTest'#p=$(cd $(dirname $0)./; pwd)work=${p}'/work/'`rm -rf ${work}`tomcatpath=${p}'/bin'echo 'operate restart tomcat: '$tomcatpathpid=`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}" ]thenrealtimelog=${p}'/logs/catalina.out'fiecho ===========realtimelog==============echo ${realtimelog}if [ -n "$pid" ]then{echo ===========shutdown================$tomcatpath'/shutdown.sh'sleep 2pid=`ps aux | grep $tomcatpath | grep -v grep | grep -v retomcat | awk '{print $2}'`if [ -n "$pid" ]then{sleep 2echo ========kill tomcat begin==============echo $pidkill -9 $pidecho ========kill tomcat end==============}fi}elseecho ===========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:6901hostip=`hostname -I`hostdes="$hostname $hostip"# 定义要监控的页面地URLWebUrl=http://$host/DoNOTremove.txt# 日志输出GetPageInfo=/tmp/TomcatMonitor.InfoTomcatMonitorLog=/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":"$1n$2"},"at":{"atMobiles":["18888888888"],"isAtAll":false}}"`echo $res}Monitor(){echo "$hostdes"pidpath=/tmp/tcmonV1.pid #定义pid文件if [ -f "$pidpath" ] ;thenecho "[$nowtime]脚本已经在执行,准备退出"cat $pidpathexit 2elseecho $$ >$pidpath #将当前Shell进程号记录到pid文件中echo "[$nowtime]脚本开始执行"cat $pidpathecho "[$nowtime][info]开始监测linux服务器..."res=`curl -XPOST -m 10 -s -L -H "Content-Type:application/json" -H "charset:utf-8" $WebUrl `echo $resif [[ $res =~ "hello running" ]] ;thenecho "[$nowtime][success]tomcat运行正常,接口访问正常......"elseecho "$nowtime][error]访问失败,系统准备自动重启"body="[$nowtime][error]访问失败,系统准备自动重启"SendMessageToDingding $subject $body# 定义要重启的脚本路径cd /usr/tomcatTest/bin/./restartup.shsleep 5m# 检查是否重启成功res=`curl -m 30 -XPOST -s -L -H "Content-Type:application/json" -H "charset:utf-8" $WebUrl `echo $resif [[ $res =~ "hello running" ]] ;thenecho "[$nowtime][success]tomcat重启成功,接口访问正常......"body="[$nowtime][success]tomcat重启成功,接口访问正常......"SendMessageToDingding $subject $bodyelseecho "[$nowtime][error]tomcat启动可能失败或者重启超时,请管理员检查"body="[$nowtime][error]tomcat启动可能失败或者重启超时,请管理员检查"SendMessageToDingding $subject $bodyfifiecho "[$nowtime][info]结束监测linux服务器..."rm -f $pidpathfi}Monitor>>$TomcatMonitorLog
测试直接执行脚本。(
如果执行新建脚本提示没有权限,可通过 chmod u+x *.sh ,为当前用户增加可执行权限)
脚本执行,已检测到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
查看脚本执行日志
可以看到脚本正常执行,日志打印也正常。接下来写一个会抛出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));
}
}
经测试,监控状态正常。
最后
以上就是无聊雨为你收集整理的JVM OOM监控加服务重启 Linux脚本的全部内容,希望文章能够帮你解决JVM OOM监控加服务重启 Linux脚本所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
发表评论 取消回复