Tomcat进程意外退出、Tomcat内存溢出(Tomcat memory leak / await A valid shutdown/clearReferencesJdbc )

1. 服务器报错信息片段

28-Nov-2018 17:15:08.698 信息 [main] org.apache.catalina.core.StandardServer.await A valid shutdown command was received via the shutdown port. Stopping the Server instance.
28-Nov-2018 17:15:08.699 信息 [main] org.apache.coyote.AbstractProtocol.pause Pausing ProtocolHandler ["http-nio-4104"]
28-Nov-2018 17:15:08.751 信息 [main] org.apache.coyote.AbstractProtocol.pause Pausing ProtocolHandler ["ajp-nio-8009"]
28-Nov-2018 17:15:08.802 信息 [main] org.apache.catalina.core.StandardService.stopInternal Stopping service [Catalina]
28-Nov-2018 17:15:08.828 信息 [localhost-startStop-2] org.apache.catalina.core.StandardWrapper.unload Waiting for [15] instance(s) to be deallocated for Servlet [CoreSrv]
28-Nov-2018 17:15:09.830 信息 [localhost-startStop-2] org.apache.catalina.core.StandardWrapper.unload Waiting for [15] instance(s) to be deallocated for Servlet [CoreSrv]
28-Nov-2018 17:15:10.832 信息 [localhost-startStop-2] org.apache.catalina.core.StandardWrapper.unload Waiting for [15] instance(s) to be deallocated for Servlet [CoreSrv]
28-Nov-2018 17:15:10.944 警告 [localhost-startStop-2] org.apache.catalina.loader.WebappClassLoaderBase.clearReferencesJdbc The web application [ROOT] registered the JDBC driver [oracle.jdbc.OracleDriver] but failed to unregister it when the web application was stopped. To prevent a memory leak, the JDBC Driver has been forcibly unregistered.
28-Nov-2018 17:15:10.946 警告 [localhost-startStop-2] org.apache.catalina.loader.WebappClassLoaderBase.clearReferencesThreads The web application [ROOT] appears to have started a thread named [oracle.jdbc.driver.BlockSource.ThreadedCachingBlockSource.BlockReleaser] but has failed to stop it. This is very likely to create a memory leak. Stack trace of thread:
java.lang.Object.wait(Native Method)

2. 五种原因分析

无可置疑,shutdownhook被触发,执行了销毁逻辑,肯定是哪里发起了命令,按照这个思路进行可能性排除查证即可。

  1. tomcat server.xml配置问题
  2. 程序里System.exit或系统发出的SIGKILL信号、数据源链接未关闭等
  3. 持续集成工具如Jenkins等的命令触发
  4. 停起服命令问题
  5. tomcat catalina.sh 的shell脚本问题

注意:网上所说的server.xml中把Listener 监听关闭的说法治标不治本,建议读者按如下思路查证问题根由。*

2.1 tomcat server.xml配置问题

  1. 原理分析:原文

tomcat 对JDBC Driver的清理, 释放引用是clearReferencesJdbc方法,它检查当前WebappClassLoader加载过的,在关闭时未注销掉的JDBC Driver,给出警告信息,并强行将这些Driver反注册掉,servlet在初始化时注册了一个Driver,但销毁时未将这个Driver给反注册掉( unregistered);这时不管是显式的通过命令来stop tomcat,还是因为设置了自动reload,而且恰好检查到应用有变,执行了reload的时候(reload也是对app context进行stop,然后再重新start),就会被tomcat判断为泄露,给出警告并强制反注册Driver:


解决:调整配置为reloadable="false"
补充:tomcat热部署配置建议也关掉autoDeploy="false"

2.1 程序里System.exit或系统发出的SIGKILL信号、数据源链接未关闭等

  1. 程序代码里含有System.exit()等方法退出jvm,导致发起shutdown信号,tomcat进程关闭,进而导致内存泄露。
  2. 程序开发时,对于数据源的操作,只open没有close,当访问量级上来的时候,就会导致内存溢出。尤其是DBManager的操作,如下面代码块样例,如果不进行close操作,就会出现问题。

ps:即使finaly里含有close,但是程序里如果出现死循环或者System.exit()也会导致数据源无法关闭。

        DBManager dbManager = new DBManager();
        BLPrpElecHistoryAction blPrpElecHistoryAction = new BLPrpElecHistoryAction();
        try{
            dbManager.open("prpDataSource");
            dbManager.beginTransaction();
            //插入记录
            blPrpElecHistoryAction.insert(dbManager,prpElecHistoryDto);
            dbManager.commitTransaction();
        }catch(Exception exception){
            dbManager.rollbackTransaction();
            throw exception;
        }finally{
            //dbManager.close();
        }

2.3 持续集成工具如Jenkins等的命令触发

前面说到,出现第一行报错的情况肯定是哪里发出了shutdown信号,才导致中止tomcat进程动作,那么如果排除了所有当前系统服务器的问题之后,也有可能是例如Jenkins等持续集成的自动化工具配置了相关操作命令,发出相关信号导致。

2.4 SSH停起服命令问题

我们在操作linux时,大多都会使用SSH工具通过命令对tomcat进行停起服,命令一般为 ./shutdown.sh./startup.sh ,大家为了监控方便,会把日志打印在后台(bin目录下时命令为:) ./startup.sh & tail -f …/logs/catalina.out ,如果我们不执行ctrl+c而直接关闭SSH终端,就会导致tomcat线程中断,从而引发内存溢出问题。
解决:停服命令前面加nohup或者调整catalina.sh脚本开启作业控制set -m以保障tomcat线程不会因为中断的不正当操作而中断。


  1. nohup命令会在SSH中断关闭时,使tomcat进程不挂的运行;
  2. set -m 开启作业控制样例
    #!/bin/bash
    set -m
    cd /home/admin/tt/tomcat/bin/
    ./catalina.sh start
    tail -f /home/admin/tt/tomcat/logs/catalina.out

2.5 tomcat catalina.sh 的shell脚本问题

这也是可能性最大的一种情况,也是tomcat服务对JVM内存分配配置的重要环节之一。由于永久区内存分配不够导致内存溢出,或者是最大值与最小值分配不合理引起。分析过程参考:原文

2.5.1 JAVA_OPTS配置调整**

打开在Tomcat的安装目录的bin文件的catalina.sh文件,进入编辑状态:
在注释后面加上如下脚本:
JAVA_OPTS="$JAVA_OPTS -server -Xms512m -Xmx1024m’ -XX:PermSize=64M -XX:MaxPermSize=256m"其中
-Xms512m -Xmx1024m是设置Tomcat使用的内存的大小.
-XX:PermSize=64M -XX:MaxPermSize=256m 指定类空间(用于加载类)的内存大小
(1)-Xms,jvm启动时,初始分配的堆/栈内存
(2)-Xmx,JVM最大允许分配的堆/栈内存,按需分配
(3)-Xss,设定每个线程的堆栈大小
(4)-XX:PermSize,JVM初始分配的非堆内存
(5)-XX:MaxPermSize,JVM最大允许分配的非堆内存,按需分配


Java 自带性能监控工具
拓展:这里还可以配置jdk8的监控工具配置,以便用JDKHome/bin/jmc.exe工具进行监控
CATALINA_OPTS="-javaagent:/home/tomcat/skywalking-agent/skywalking-agent.jar -Djava.rmi.server.hostname=要监控的IP地址 -Djavax.management.builder.initial= -Dcom.sun.management.jmxremote=true -Dcom.sun.management.jmxremote.port=12345 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false"

2.5.2 JDK8+移除了Perm,引入了Metapsace

它们两者的区别是什么呢?Metasace上面已经总结了,无论-XX:MetaspaceSize和-XX:MaxMetaspaceSize两个参数如何设置,都会从20.8M开始(JDK7 Perm 设置-XX:PermSize=64m -XX:MaxPermSize=64m,那么PC初始化始终是64m),随着类加载越来越多不断扩容调整,上限是-XX:MaxMetaspaceSize,默认是几乎无穷大。而Perm的话,我们通过配置-XX:PermSize以及-XX:MaxPermSize来控制这块内存的大小,jvm在启动的时候会根据-XX:PermSize初始化分配一块连续的内存块,这样的话,如果-XX:PermSize设置过大,就是一种赤果果的浪费。很明显,Metapsace比Perm更灵活也更安全.


JDK8 例子如下
JAVA_OPTS="-server -Xms4096m -Xmx4096m -XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=256m -XX:MinMetaspaceFreeRatio=40 -XX:MaxMetaspaceFreeRatio=70 -XX:+PrintGCDateStamps -Xloggc:/home/tomcat/apache-tomcat-8.5.31/logs/myapp-gc.log -XX:+HeapDumpOnOutOfMemoryError -XX:+UseConcMarkSweepGC -XX:+CMSParallelRemarkEnabled -XX:LargePageSizeInBytes=128m -XX:+UseFastAccessorMethods -XX:+UseCMSInitiatingOccupancyOnly -XX:CMSInitiatingOccupancyFraction=70 -Duser.timezone=GMT+8 -Dsun.rmi.dgc.client.gcInterval=36000000"


2.5.3 Metaspace-元空间的相关配置说明

Metaspace使用的是本地内存,而不是堆内存,也就是说在默认情况下Metaspace的大小只与本地内存大小有关。当然你也可以通过以下的几个参数对Metaspace进行控制:
-XX:MetaspaceSize=N
这个参数是初始化的Metaspace大小,该值越大触发Metaspace GC的时机就越晚。随着GC的到来,虚拟机会根据实际情况调控Metaspace的大小,可能增加上线也可能降低。在默认情况下,这个值大小根据不同的平台在12M到20M浮动。使用java -XX:+PrintFlagsInitial命令查看本机的初始化参数,-XX:Metaspacesize为21810376B(大约20.8M)。
-XX:MaxMetaspaceSize=N
这个参数用于限制Metaspace增长的上限,防止因为某些情况导致Metaspace无限的使用本地内存,影响到其他程序。在本机上该参数的默认值为4294967295B(大约4096MB)。
-XX:MinMetaspaceFreeRatio=N
当进行过Metaspace GC之后,会计算当前Metaspace的空闲空间比,如果空闲比小于这个参数,那么虚拟机将增长Metaspace的大小。在本机该参数的默认值为40,也就是40%。设置该参数可以控制Metaspace的增长的速度,太小的值会导致Metaspace增长的缓慢,Metaspace的使用逐渐趋于饱和,可能会影响之后类的加载。而太大的值会导致Metaspace增长的过快,浪费内存。
-XX:MaxMetasaceFreeRatio=N
当进行过Metaspace GC之后, 会计算当前Metaspace的空闲空间比,如果空闲比大于这个参数,那么虚拟机会释放Metaspace的部分空间。在本机该参数的默认值为70,也就是70%。
-XX:MaxMetaspaceExpansion=N
Metaspace增长时的最大幅度。在本机上该参数的默认值为5452592B(大约为5MB)。
-XX:MinMetaspaceExpansion=N
Metaspace增长时的最小幅度。在本机上该参数的默认值为340784B(大约330KB为)。

2.5.4 常用的分析内存linux命令

如下为在linux系统上常用的进程查看命令

  1. 查看pid:pidof java
  2. 查看可选项:jstat -options
  3. 根据pid查看各项内存使用情况:jstat -gcnewcapacity pid
  4. jmap -heap pid
    命令样例

第一次发布博客,写的不好大家多多见谅,希望对你有帮助,欢迎您的关注,请多多指教~

猜你喜欢

转载自blog.csdn.net/u012723183/article/details/84677092