作者 | 程序员自由之路

来源 | cnblogs.com/54chensongxia/p/13234398.html

Tomcat的启停脚本源码解析

Tomcat是一款我们平时开发过程中最常用到的Servlet容器。本系列博客会记录Tomcat的整体架构、主要组件、IO线程模型、请求在Tomcat内部的流转过程以及一些Tomcat调优的相关知识。

力求达到以下几个目的:

  • 更加熟悉Tomcat的工作机制,工作中遇到Tomcat相关问题能够快速定位,从源头来解决;

  • 对于一些高并发场景能够对Tomcat进行调优;

  • 通过对Tomcat源码的分析,吸收一些Tomcat的设计的理念,应用到自己的软件开发过程中。


Tomcat的启动和停止是通过startup.bat和shutdown.bat这两个脚本实现的。本篇博客就分析下这两个脚本的主要执行流程。

1. startup.bat脚本分析

//关闭命令自身输出@echo off//setlocal命令表示,这边对环境变量的修改只对当前脚本生效setlocal//检查CATALINA_HOME这个环境变量有没设置,如果有设置就使用设置的环境变量//如果没设置,将CATALINA_HOME设置成当前目录。//检测%CATALINA_HOME%\bin\catalina.bat这个脚本存不存在,不存在整合脚本结束,报错rem Guess CATALINA_HOME if not definedset "CURRENT_DIR=%cd%"if not "%CATALINA_HOME%" == "" goto gotHomeset "CATALINA_HOME=%CURRENT_DIR%"if exist "%CATALINA_HOME%\bin\catalina.bat" goto okHomecd ..set "CATALINA_HOME=%cd%"cd "%CURRENT_DIR%":gotHomeif exist "%CATALINA_HOME%\bin\catalina.bat" goto okHomeecho The CATALINA_HOME environment variable is not defined correctlyecho This environment variable is needed to run this programgoto end:okHome//准备启动脚本set "EXECUTABLE=%CATALINA_HOME%\bin\catalina.bat"

rem Check that target executable existsif exist "%EXECUTABLE%" goto okExececho Cannot find "%EXECUTABLE%"echo This file is needed to run this programgoto end:okExec//拼接catalina.bat这个脚本的命令行参数rem Get remaining unshifted command line arguments and save them in theset CMD_LINE_ARGS=:setArgsif ""%1""=="""" goto doneSetArgsset CMD_LINE_ARGS=%CMD_LINE_ARGS% %1shiftgoto setArgs:doneSetArgs//执行catalina.bat这个脚本,执行start,并添加命令行参数call "%EXECUTABLE%" start %CMD_LINE_ARGS%

:end

整个startup.bat脚本很简单,根据CATALINA_HOME检测catalina.bat是否存在,不存在的话就报错,存在的话拼接命令行参数然后执行catalina.bat这个脚本。CATALINA_HOME这个环境变量的取值逻辑如下图所示:

如果环境变量设置了CATALINA_HOME,则直接使用环境变量设置的值作为Tomcat安装目录。假如未设置环境变量CATALINA_HOME,则以当前目录作为CATALINA_HOME。此时,如果%CATALINA_HOME%\bin\catalina.bat存在,则批处理或命令行当前目录作为CATALINA_HOME。假如%CATALINA_HOME%\bin\catalina.bat不存在,则把当前目录的上一级目录作为CATALINA_HOME,然后再判断%CATALINA_HOME%\bin\catalina.bat是否存在。如果存在,则上一级目录就是CATALINA_HOME;否则,提示找不到CATALINA_HOME环境变量并结束执行。

我们可以看出来,正真执行的脚本是catalina.bat这个脚本,那为什么还要整个startup.bat脚本呢?

其实这个startup.bat脚本就是提供给使用者用来修改的,我们可以在其中设置JAVA_HOME,CATALINA_HOME等环境变量,但我们并不需要深入到较为复杂的catalina.bat脚本中,这正是startup.bat脚本的真正用意所在。

我们知道,软件设计模式中有一个重要的原则就是开闭原则,即我们可以允许别人扩展我们的程序,但在程序发布后,我们拒绝任何修改,因为修改会产生新的Bug,使得我们已经Bug-free的程序又要重新测试。开闭原则是面向对象世界中的一个非常重要的原则,我们可以把这个原则从Java类扩展至源代码级别。startup脚本就是要求用户不要修改catalina.bat脚本,这是符合软件设计思想的。我们如果想要彻底贯彻这个重要的软件设计原则,可以写一个新脚本tomcat.bat,脚本内容大致如下:

set JAVA_HOME=C:\Program Files\Java\jdk1.5.0_09set CATALINA_HOME=C:\carl\it\tomcat_research\jakarta-tomcat-5.0.28   call %CATALINA_HOME%\bin\startup.bat

这个tomcat.bat文件可以存放在任何目录并能执行,并且不需要修改tomcat自带的任何脚本及其它环境变量,这就彻底贯彻了开闭原则。

2. catalina.bat脚本简析

当startup脚本完成环境变量的设置后,就开始调用catalina.bat脚本来启动Tomcat。Catalina脚本的主要任务是根据环境变量和不同的命令行参数,拼凑出完整的java命令,调用Tomcat的主启动类org.apache.catalina.startup.Bootstrap来启动Tomcat。

@echo offrem ---------------------------------------------------------------------------rem Start/Stop Script for the CATALINA Serverremrem Environment Variable Prerequisitesremrem   Do not set the variables in this script. Instead put them into a scriptrem   setenv.bat in CATALINA_BASE/bin to keep your customizations separate.remrem   WHEN RUNNING TOMCAT AS A WINDOWS SERVICE:rem   Note that the environment variables that affect the behavior of thisrem   script will have no effect at all on Windows Services. As such, anyrem   local customizations made in a CATALINA_BASE/bin/setenv.bat scriptrem   will also have no effect on Tomcat when launched as a Windows Service.rem   The configuration that controls Windows Services is stored in the Windowsrem   Registry, and is most conveniently maintained using the "tomcatXw.exe"rem   maintenance utility, where "X" is the major version of Tomcat you arerem   running.remrem   CATALINA_HOME May point at your Catalina "build" directory.remrem   CATALINA_BASE (Optional) Base directory for resolving dynamic portionsrem                   of a Catalina installation. If not present, resolves torem                   the same directory that CATALINA_HOME points to.remrem   CATALINA_OPTS (Optional) Java runtime options used when the "start",rem                   "run" or "debug" command is executed.rem                   Include here and not in JAVA_OPTS all options, that shouldrem                   only be used by Tomcat itself, not by the stop process,rem                   the version command etc.rem                   Examples are heap size, GC logging, JMX ports etc.remrem   CATALINA_TMPDIR (Optional) Directory path location of temporary directoryrem                   the JVM should use (java.io.tmpdir). Defaults torem                   %CATALINA_BASE%\temp.remrem   JAVA_HOME Must point at your Java Development Kit installation.rem                   Required to run the with the "debug" argument.remrem   JRE_HOME Must point at your Java Runtime installation.rem                   Defaults to JAVA_HOME if empty. If JRE_HOME and JAVA_HOMErem                   are both set, JRE_HOME is used.remrem   JAVA_OPTS (Optional) Java runtime options used when any commandrem                   is executed.rem                   Include here and not in CATALINA_OPTS all options, thatrem                   should be used by Tomcat and also by the stop process,rem                   the version command etc.rem                   Most options should go into CATALINA_OPTS.remrem   JPDA_TRANSPORT (Optional) JPDA transport used when the "jpda start"rem                   command is executed. The default is "dt_socket".remrem   JPDA_ADDRESS (Optional) Java runtime options used when the "jpda start"rem                   command is executed. The default is localhost:8000.remrem   JPDA_SUSPEND (Optional) Java runtime options used when the "jpda start"rem                   command is executed. Specifies whether JVM should suspendrem                   execution immediately after startup. Default is "n".remrem   JPDA_OPTS (Optional) Java runtime options used when the "jpda start"rem                   command is executed. If used, JPDA_TRANSPORT, JPDA_ADDRESS,rem                   and JPDA_SUSPEND are ignored. Thus, all required jpdarem                   options MUST be specified. The default is:remrem                   -agentlib:jdwp=transport=%JPDA_TRANSPORT%,rem                       address=%JPDA_ADDRESS%,server=y,suspend=%JPDA_SUSPEND%remrem   JSSE_OPTS (Optional) Java runtime options used to control the TLSrem                   implementation when JSSE is used. Default is:rem                   "-Djdk.tls.ephemeralDHKeySize=2048"remrem   LOGGING_CONFIG (Optional) Override Tomcat's logging config filerem Example (all one line)rem set LOGGING_CONFIG="-Djava.util.logging.config.file=%CATALINA_BASE%\conf\logging.properties"remrem LOGGING_MANAGER (Optional) Override Tomcat's logging managerrem                   Example (all one line)rem                   set LOGGING_MANAGER="-Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager"remrem   TITLE (Optional) Specify the title of Tomcat window. The defaultrem                   TITLE is Tomcat if it's not specified.rem Example (all one line)rem set TITLE=Tomcat.Cluster#1.Server#1 [%DATE% %TIME%]rem ---------------------------------------------------------------------------setlocalrem Suppress Terminate batch job on CTRL+Cif not ""%1"" == ""run"" goto mainEntryif "%TEMP%" == "" goto mainEntryif exist "%TEMP%\%~nx0.run" goto mainEntryecho Y>"%TEMP%\%~nx0.run"if not exist "%TEMP%\%~nx0.run" goto mainEntryecho Y>"%TEMP%\%~nx0.Y"call "%~f0" %* rem Use provided errorlevelset RETVAL=%ERRORLEVEL%del /Q "%TEMP%\%~nx0.Y" >NUL 2>&1exit /B %RETVAL%:mainEntrydel /Q "%TEMP%\%~nx0.run" >NUL 2>&1//防止用户直接执行catalina.bat这个脚本,再次检测下CATALINA_HOME环境变量//检测catalina.bat这个脚本存不存在rem Guess CATALINA_HOME if not definedset "CURRENT_DIR=%cd%"if not "%CATALINA_HOME%" == "" goto gotHomeset "CATALINA_HOME=%CURRENT_DIR%"if exist "%CATALINA_HOME%\bin\catalina.bat" goto okHomecd ..set "CATALINA_HOME=%cd%"cd "%CURRENT_DIR%":gotHomeif exist "%CATALINA_HOME%\bin\catalina.bat" goto okHomeecho The CATALINA_HOME environment variable is not defined correctlyecho This environment variable is needed to run this programgoto end:okHome//检测CATALINA_BASE是否存在,不存在就设置成和CATALINA_HOME一致rem Copy CATALINA_BASE from CATALINA_HOME if not definedif not "%CATALINA_BASE%" == "" goto gotBaseset "CATALINA_BASE=%CATALINA_HOME%":gotBaserem Ensure that neither CATALINA_HOME nor CATALINA_BASE contains a semi-colonrem as this is used as the separator in the classpath and Java provides norem mechanism for escaping if the same character appears in the path. Check thisrem by replacing all occurrences of ';' with '' and checking that neitherrem CATALINA_HOME nor CATALINA_BASE have changedif "%CATALINA_HOME%" == "%CATALINA_HOME:;=%" goto homeNoSemicolonecho Using CATALINA_HOME: "%CATALINA_HOME%"echo Unable to start as CATALINA_HOME contains a semicolon (;) charactergoto end:homeNoSemicolon

if "%CATALINA_BASE%" == "%CATALINA_BASE:;=%" goto baseNoSemicolonecho Using CATALINA_BASE: "%CATALINA_BASE%"echo Unable to start as CATALINA_BASE contains a semicolon (;) charactergoto end:baseNoSemicolon

rem Ensure that any user defined CLASSPATH variables are not used on startup,rem but allow them to be specified in setenv.bat, in rare case when it is needed.set CLASSPATH=

//如果%CATALINA_BASE%\bin\setenv.bat存在,执行setenv.bat这个脚本,不存在//执行%CATALINA_HOME%\bin\setenv.bat这个脚本来设置环境变量rem Get standard environment variablesif not exist "%CATALINA_BASE%\bin\setenv.bat" goto checkSetenvHomecall "%CATALINA_BASE%\bin\setenv.bat"goto setenvDone:checkSetenvHomeif exist "%CATALINA_HOME%\bin\setenv.bat" call "%CATALINA_HOME%\bin\setenv.bat":setenvDone

//如果%CATALINA_HOME%\bin\setclasspath.bat存在,执行setclasspath.bat,这个脚本的主要作用是检测//JAVA_HOME有没有正确设置rem Get standard Java environment variablesif exist "%CATALINA_HOME%\bin\setclasspath.bat" goto okSetclasspathecho Cannot find "%CATALINA_HOME%\bin\setclasspath.bat"echo This file is needed to run this programgoto end:okSetclasspathcall "%CATALINA_HOME%\bin\setclasspath.bat" %1if errorlevel 1 goto end

//将bootstrap.jar加入classpathrem Add on extra jar file to CLASSPATHrem Note that there are no quotes as we do not want to introduce randomrem quotes into the CLASSPATHif "%CLASSPATH%" == "" goto emptyClasspathset "CLASSPATH=%CLASSPATH%;":emptyClasspathset "CLASSPATH=%CLASSPATH%%CATALINA_HOME%\bin\bootstrap.jar"

//设置CATALINA_TMPDIR=%CATALINA_BASE%\tempif not "%CATALINA_TMPDIR%" == "" goto gotTmpdirset "CATALINA_TMPDIR=%CATALINA_BASE%\temp":gotTmpdir

//将tomcat-juli.jar加入classpathrem Add tomcat-juli.jar to classpathrem tomcat-juli.jar can be over-ridden per instanceif not exist "%CATALINA_BASE%\bin\tomcat-juli.jar" goto juliClasspathHomeset "CLASSPATH=%CLASSPATH%;%CATALINA_BASE%\bin\tomcat-juli.jar"goto juliClasspathDone:juliClasspathHomeset "CLASSPATH=%CLASSPATH%;%CATALINA_HOME%\bin\tomcat-juli.jar":juliClasspathDone

//如果没有设置JSSE_OPTS,JSSE_OPTS="-Djdk.tls.ephemeralDHKeySize=2048"//再将JAVA_OPTS设置成"JAVA_OPTS=%JAVA_OPTS% %JSSE_OPTS%"if not "%JSSE_OPTS%" == "" goto gotJsseOptsset JSSE_OPTS="-Djdk.tls.ephemeralDHKeySize=2048":gotJsseOptsset "JAVA_OPTS=%JAVA_OPTS% %JSSE_OPTS%"

rem Register custom URL handlersrem Do this here so custom URL handles (specifically 'war:...') can be used in the security policyset "JAVA_OPTS=%JAVA_OPTS% -Djava.protocol.handler.pkgs=org.apache.catalina.webresources"

//将日志配置文件设置成logging.propertiesif not "%LOGGING_CONFIG%" == "" goto noJuliConfigset LOGGING_CONFIG=-Dnopif not exist "%CATALINA_BASE%\conf\logging.properties" goto noJuliConfigset LOGGING_CONFIG=-Djava.util.logging.config.file="%CATALINA_BASE%\conf\logging.properties":noJuliConfig

//配置默认的LOGGING_MANAGERif not "%LOGGING_MANAGER%" == "" goto noJuliManagerset LOGGING_MANAGER=-Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager:noJuliManager

rem ----- Execute The Requested Command ---------------------------------------

echo Using CATALINA_BASE: "%CATALINA_BASE%"echo Using CATALINA_HOME: "%CATALINA_HOME%"echo Using CATALINA_TMPDIR: "%CATALINA_TMPDIR%"if ""%1"" == ""debug"" goto use_jdkecho Using JRE_HOME: "%JRE_HOME%"goto java_dir_displayed:use_jdkecho Using JAVA_HOME: "%JAVA_HOME%":java_dir_displayedecho Using CLASSPATH: "%CLASSPATH%"

set _EXECJAVA=%_RUNJAVA%set MAINCLASS=org.apache.catalina.startup.Bootstrapset ACTION=startset SECURITY_POLICY_FILE=set DEBUG_OPTS=set JPDA=

if not ""%1"" == ""jpda"" goto noJpdaset JPDA=jpdaif not "%JPDA_TRANSPORT%" == "" goto gotJpdaTransportset JPDA_TRANSPORT=dt_socket:gotJpdaTransportif not "%JPDA_ADDRESS%" == "" goto gotJpdaAddressset JPDA_ADDRESS=localhost:8000:gotJpdaAddressif not "%JPDA_SUSPEND%" == "" goto gotJpdaSuspendset JPDA_SUSPEND=n:gotJpdaSuspendif not "%JPDA_OPTS%" == "" goto gotJpdaOptsset JPDA_OPTS=-agentlib:jdwp=transport=%JPDA_TRANSPORT%,address=%JPDA_ADDRESS%,server=y,suspend=%JPDA_SUSPEND%:gotJpdaOptsshift:noJpda

if ""%1"" == ""debug"" goto doDebugif ""%1"" == ""run"" goto doRunif ""%1"" == ""start"" goto doStartif ""%1"" == ""stop"" goto doStopif ""%1"" == ""configtest"" goto doConfigTestif ""%1"" == ""version"" goto doVersion

echo Usage: catalina ( commands ... )echo commands:echo debug Start Catalina in a debuggerecho debug -security   Debug Catalina with a security managerecho jpda start        Start Catalina under JPDA debuggerecho run Start Catalina in the current windowecho run -security     Start in the current window with security managerecho start             Start Catalina in a separate windowecho start -security   Start in a separate window with security managerecho stop              Stop Catalinaecho configtest Run a basic syntax check on server.xmlecho version           What version of tomcat are you running?goto end

:doDebugshiftset _EXECJAVA=%_RUNJDB%set DEBUG_OPTS=-sourcepath "%CATALINA_HOME%\..\..\java"if not ""%1"" == ""-security"" goto execCmdshiftecho Using Security Managerset "SECURITY_POLICY_FILE=%CATALINA_BASE%\conf\catalina.policy"goto execCmd

:doRunshiftif not ""%1"" == ""-security"" goto execCmdshiftecho Using Security Managerset "SECURITY_POLICY_FILE=%CATALINA_BASE%\conf\catalina.policy"goto execCmd

:doStartshiftif "%TITLE%" == "" set TITLE=Tomcatset _EXECJAVA=start "%TITLE%" %_RUNJAVA%if not ""%1"" == ""-security"" goto execCmdshiftecho Using Security Managerset "SECURITY_POLICY_FILE=%CATALINA_BASE%\conf\catalina.policy"goto execCmd

:doStopshiftset ACTION=stopset CATALINA_OPTS=goto execCmd

:doConfigTestshiftset ACTION=configtestset CATALINA_OPTS=goto execCmd

:doVersion%_EXECJAVA% -classpath "%CATALINA_HOME%\lib\catalina.jar" org.apache.catalina.util.ServerInfogoto end

:execCmdrem Get remaining unshifted command line arguments and save them in theset CMD_LINE_ARGS=:setArgsif ""%1""=="""" goto doneSetArgsset CMD_LINE_ARGS=%CMD_LINE_ARGS% %1shiftgoto setArgs:doneSetArgs

rem Execute Java with the applicable propertiesif not "%JPDA%" == "" goto doJpdaif not "%SECURITY_POLICY_FILE%" == "" goto doSecurity

//一般情况下都会执行到这个脚本语句%_EXECJAVA% %LOGGING_CONFIG% %LOGGING_MANAGER% %JAVA_OPTS% %CATALINA_OPTS% %DEBUG_OPTS% -classpath "%CLASSPATH%" -Dcatalina.base="%CATALINA_BASE%" -Dcatalina.home="%CATALINA_HOME%" -Djava.io.tmpdir="%CATALINA_TMPDIR%" %MAINCLASS% %CMD_LINE_ARGS% %ACTION%goto end:doSecurity%_EXECJAVA% %LOGGING_CONFIG% %LOGGING_MANAGER% %JAVA_OPTS% %CATALINA_OPTS% %DEBUG_OPTS% -classpath "%CLASSPATH%" -Djava.security.manager -Djava.security.policy=="%SECURITY_POLICY_FILE%" -Dcatalina.base="%CATALINA_BASE%" -Dcatalina.home="%CATALINA_HOME%" -Djava.io.tmpdir="%CATALINA_TMPDIR%" %MAINCLASS% %CMD_LINE_ARGS% %ACTION%goto end:doJpdaif not "%SECURITY_POLICY_FILE%" == "" goto doSecurityJpda%_EXECJAVA% %LOGGING_CONFIG% %LOGGING_MANAGER% %JAVA_OPTS% %JPDA_OPTS% %CATALINA_OPTS% %DEBUG_OPTS% -classpath "%CLASSPATH%" -Dcatalina.base="%CATALINA_BASE%" -Dcatalina.home="%CATALINA_HOME%" -Djava.io.tmpdir="%CATALINA_TMPDIR%" %MAINCLASS% %CMD_LINE_ARGS% %ACTION%goto end:doSecurityJpda%_EXECJAVA% %LOGGING_CONFIG% %LOGGING_MANAGER% %JAVA_OPTS% %JPDA_OPTS% %CATALINA_OPTS% %DEBUG_OPTS% -classpath "%CLASSPATH%" -Djava.security.manager -Djava.security.policy=="%SECURITY_POLICY_FILE%" -Dcatalina.base="%CATALINA_BASE%" -Dcatalina.home="%CATALINA_HOME%" -Djava.io.tmpdir="%CATALINA_TMPDIR%" %MAINCLASS% %CMD_LINE_ARGS% %ACTION%goto end

:end

总结下catalina.bat的整个执行流程:

  • 检查并设置CATALINA_HOME环境变量,检测%CATALINA_HOME%\bin\catalina.bat这个脚本是否存在,不存在直接报错;

  • 检测CATALINA_BASE是否存在,不存在就设置成和CATALINA_HOME一致;

  • 如果%CATALINA_BASE%\bin\setenv.bat这个脚本存在,执行setenv.bat这个脚本,不存在的话执行%CATALINA_HOME%\bin\setenv.bat这个脚本来设置环境变量,都不存在继续往下执行;

  • 如果%CATALINA_HOME%\bin\setclasspath.bat存在,执行setclasspath.bat,这个脚本的主要作用是检测
    JAVA_HOME有没有正确设置;

  • 将bootstrap.jar加入classpath;

  • 设置CATALINA_TMPDIR=%CATALINA_BASE%\temp;

  • 将tomcat-juli.jar加入classpath;

  • 如果没有设置环境变量JSSE_OPTS,默认设置JSSE_OPTS="-Djdk.tls.ephemeralDHKeySize=2048"
    再将JAVA_OPTS设置成"JAVA_OPTS=%JAVA_OPTS% %JSSE_OPTS%"

  • 通过LOGGING_CONFIG变量,将日志配置文件设置成%CATALINA_BASE%\conf\logging.properties

  • 配置默认的LOGGING_MANAGER;

  • 拼写java执行命令。

拼接命令的代码主要是下面这段:

%_EXECJAVA% %LOGGING_CONFIG% %LOGGING_MANAGER% %JAVA_OPTS% %CATALINA_OPTS% %DEBUG_OPTS% -classpath "%CLASSPATH%" -Dcatalina.base="%CATALINA_BASE%" -Dcatalina.home="%CATALINA_HOME%" -Djava.io.tmpdir="%CATALINA_TMPDIR%" %MAINCLASS% %CMD_LINE_ARGS% %ACTION%

通过这段代码我们可以看到Tomcat在启动的时候配置了哪些参数。我们执行下面的命令:

startup.bat arg1 arg2

实际执行的命令如下:

"start "Tomcat" "C:\Program Files\Java\jdk1.8.0_73\bin\java.exe" -Djava.util.logging.config.file="D:\software\tomcat-64\apache-tomcat-9.0.0.M21-windows-x64 (1)\apache-tomcat-9.0.0.M21\conf\logging.properties" -Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager "-Djdk.tls.ephemeralDHKeySize=2048" -Djava.protocol.handler.pkgs=org.apache.catalina.webresources -classpath "D:\software\tomcat-64\apache-tomcat-9.0.0.M21-windows-x64 (1)\apache-tomcat-9.0.0.M21\bin\bootstrap.jar;D:\software\tomcat-64\apache-tomcat-9.0.0.M21-windows-x64 (1)\apache-tomcat-9.0.0.M21\bin\tomcat-juli.jar" -Dcatalina.base="D:\software\tomcat-64\apache-tomcat-9.0.0.M21-windows-x64 (1)\apache-tomcat-9.0.0.M21" -Dcatalina.home="D:\software\tomcat-64\apache-tomcat-9.0.0.M21-windows-x64 (1)\apache-tomcat-9.0.0.M21" -Djava.io.tmpdir="D:\software\tomcat-64\apache-tomcat-9.0.0.M21-windows-x64 (1)\apache-tomcat-9.0.0.M21\temp" org.apache.catalina.startup.Bootstrap arg1 arg2 start"

上面命令中开头的start Tomcat的意思是重新开启一个叫tomcat的窗口执行Java命令。

我们看到上面的代码中,有个jpda模式,它是Java平台调试体系结构,可以提供很方便的远程调试,一般情况下我们不会用到这个模式。如果我们想启动这个模式的话可以执行catalina.bat jpda start这个命令。这个模式下我们可以对另外的环境变量JPDA_OPTS进行配置。

3. 关于配置的一些建议

通过上面的脚本,我们可以看到在启动过程中我们可以配置很多环境变量。

  • CATALINA_HOME:可以不配置,默认使用安装目录;

  • CATALINA_BASE:建议不要自己配置,不配置的话会自动配置成和CATALINA_HOME一致;

  • CATALINA_OPTS:可以配置;

  • CATALINA_TMPDIR:建议不要自己配置,默认%CATALINA_BASE%\temp;

  • JAVA_OPTS:可以配置;

  • JSSE_OPTS:不建议自己配置,默认值-Djdk.tls.ephemeralDHKeySize=2048;

  • LOGGING_CONFIG:建议不要自己配置,这个配置用于配置日志的配置文件,默认会使用LOGGING_CONFIG="-Djava.util.logging.config.file=%CATALINA_BASE%\conf\logging.properties"

  • LOGGING_MANAGER:建议不要自己配置,默认会使用LOGGING_MANAGER="-Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager"

通过上面介绍我们发现大多数配置都不需要我们自己进行配置,一般情况下我们只需要配置CATALINA_OPTS和JAVA_OPTS这两个配置选项就可以了。这两个参数都可以配置Java运行时所需的一些参数,下面给出两个列子。

在startup脚本中添加配置:

//windowsset JAVA_OPTS=-server -Xms1024m -Xmx2048m -XX:PermSize=256m -XX:MaxPermSize=512m//LinuxJAVA_OPTS="-server -Dfile.encoding=UTF-8 -Xms=512m -Xmx1024m -XX:PermSize=128m -XX:MaxPermSize=256m"

4. shutdown.bat脚本分析

setlocal

rem Guess CATALINA_HOME if not definedset "CURRENT_DIR=%cd%"if not "%CATALINA_HOME%" == "" goto gotHomeset "CATALINA_HOME=%CURRENT_DIR%"if exist "%CATALINA_HOME%\bin\catalina.bat" goto okHomecd ..set "CATALINA_HOME=%cd%"cd "%CURRENT_DIR%":gotHomeif exist "%CATALINA_HOME%\bin\catalina.bat" goto okHomeecho The CATALINA_HOME environment variable is not defined correctlyecho This environment variable is needed to run this programgoto end:okHome

set "EXECUTABLE=%CATALINA_HOME%\bin\catalina.bat"

rem Check that target executable existsif exist "%EXECUTABLE%" goto okExececho Cannot find "%EXECUTABLE%"echo This file is needed to run this programgoto end:okExec

rem Get remaining unshifted command line arguments and save them in theset CMD_LINE_ARGS=:setArgsif ""%1""=="""" goto doneSetArgsset CMD_LINE_ARGS=%CMD_LINE_ARGS% %1shiftgoto setArgs:doneSetArgs//这边是关键call "%EXECUTABLE%" stop %CMD_LINE_ARGS%

:end

我们可以发现shutdown.bat的逻辑和startup.bat的逻辑是一样的,也是先设置CATALINA_HOME,拼接命令行参数,不一样是最后执行的是catalina.bat stop。

这边还是有必要来分析下Tomcat的关闭原理的。我们知道,Tomcat中的工作线程都是demo线程,如果没有一个主线程的话那么Tomcat会立即停止运行的。(前台线程死亡后,demo线程会自动消失。)那么Tomcat是在哪里启动的这个主线程的呢?通过代码跟踪,我们发现是StandardServer这个类的await方法创建了这个主线程,这个线程hold了Tomcat程序不停止。

public void await() {        // Negative values - don't wait on port - tomcat is embedded or we just don't like ports        if (getPortWithOffset() == -2) {            // undocumented yet - for embedding apps that are around, alive.            return;        }        //        if (getPortWithOffset() == -1) {            try {                awaitThread = Thread.currentThread();                while(!stopAwait) {                    try {                        Thread.sleep( 10000 );                    } catch( InterruptedException ex ) {                        // continue and check the flag                    }                }            } finally {                awaitThread = null;            }            return;        }

        // Set up a server socket to wait on        try {            awaitSocket = new ServerSocket(getPortWithOffset(), 1,                    InetAddress.getByName(address));        } catch (IOException e) {            log.error(sm.getString("standardServer.awaitSocket.fail", address,                    String.valueOf(getPortWithOffset()), String.valueOf(getPort()),                    String.valueOf(getPortOffset())), e);            return;        }

        try {            awaitThread = Thread.currentThread();

            // Loop waiting for a connection and a valid command            while (!stopAwait) {                ServerSocket serverSocket = awaitSocket;                if (serverSocket == null) {                    break;                }

                // Wait for the next connection                Socket socket = null;                StringBuilder command = new StringBuilder();                try {                    InputStream stream;                    long acceptStartTime = System.currentTimeMillis();                    try {                        socket = serverSocket.accept();                        socket.setSoTimeout(10 * 1000); // Ten seconds                        stream = socket.getInputStream();                    } catch (SocketTimeoutException ste) {                        // This should never happen but bug 56684 suggests that                        // it does.                        log.warn(sm.getString("standardServer.accept.timeout",                                Long.valueOf(System.currentTimeMillis() - acceptStartTime)), ste);                        continue;                    } catch (AccessControlException ace) {                        log.warn("StandardServer.accept security exception: "                                + ace.getMessage(), ace);                        continue;                    } catch (IOException e) {                        if (stopAwait) {                            // Wait was aborted with socket.close()                            break;                        }                        log.error("StandardServer.await: accept: ", e);                        break;                    }

                    // Read a set of characters from the socket                    int expected = 1024; // Cut off to avoid DoS attack                    while (expected < shutdown.length()) {                        if (random == null)                            random = new Random();                        expected += (random.nextInt() % 1024);                    }                    while (expected > 0) {                        int ch = -1;                        try {                            ch = stream.read();                        } catch (IOException e) {                            log.warn("StandardServer.await: read: ", e);                            ch = -1;                        }                        // Control character or EOF (-1) terminates loop                        if (ch 32 || ch == 127) {                            break;                        }                        command.append((char) ch);                        expected--;                    }                } finally {                    // Close the socket now that we are done with it                    try {                        if (socket != null) {                            socket.close();                        }                    } catch (IOException e) {                        // Ignore                    }                }

                // Match against our command string                boolean match = command.toString().equals(shutdown);                if (match) {                    log.info(sm.getString("standardServer.shutdownViaPort"));                    break;                } else                    log.warn("StandardServer.await: Invalid command '"                            + command.toString() + "' received");            }        } finally {            ServerSocket serverSocket = awaitSocket;            awaitThread = null;            awaitSocket = null;

            // Close the server socket and return            if (serverSocket != null) {                try {                    serverSocket.close();                } catch (IOException e) {                    // Ignore                }            }        }    }

StandardServer默认监听的端口是8005端口(注意这个端口和Connector组件监听端口的区别),当发现监听到的连接的输入流中的内容与默认配置的值匹配(该值默认为字符串SHUTDOWN)则跳出循环。否则该方法会一直循环执行下去。所以只要没人向8005端口发送shutdown,这个线程就会一直运行,其他的守护线程也就不会消失。

当然我们可以将StandardServer的监听端口设置成-1(SpringBoot中内嵌的Tomcat就是这么做的),此时Tomcat不会再监听具体的端口,主线程会每10秒睡眠一次,知道我们手动将stopAwait设置为true。

知道了这个原理,我们只要将这个主线程结束掉,整个Tomcat程序就结束了。通过上面的分析,可以有两种方式来关闭Tomcat:

  • 通过shutdown.bat脚本,这个脚本最终会调用到Catalina的stopServer方法,这个方法中创建了一个Socket,并向StandardServer监听的端口发送了一个shutdown命令,主线程接收到后就退出了,其他守护线程也随之结束;

如果我们将server.xml配置文件修改成:

<Server port="8005" shutdown="GET /SHUTDOWN HTTP/1.1">

这样直接在浏览器中输入http://localhost:8005/SHUTDOWN就可以关闭Tomcat了。

  • 另外的一种方式就是直接调用StandardServer的stop方法。

  • 抖音同款樱花飘落源码

  • 蓝色精美简洁MUI后台管理模板!!!

  • 这才是程序猿夏天应该使用的骚气冰人的烂漫飘雪HTML5登录页面模版

  • 深度剖析Redis系列笔记(八) - Redis数据结构之集合(完结)

感谢点赞支持下哈 

bat脚本保存dir结果_Tomcat的启停脚本源码解析相关推荐

  1. bat脚本保存dir结果_MySQL备份脚本,应该这么写

    前言: 数据库备份的重要性不言而喻,特别是在生产环境,任何数据的丢失都可能产生严重的后果.所以,无论什么环境,我们都应该有相应的备份策略来定时备份数据库.在 MySQL 中,比较常用的逻辑备份工具是 ...

  2. 自动复制 JavaScript 脚本,JavaScript点击任意位置复制脚本源码

    其实就是一个复制脚本: 他创建了一个层,然后这个层的z-index层级很高. Javascript脚本: (function($$) {var EVAutoCopy = {trigger: null, ...

  3. autojs脚本,华为手机自动解锁,解除屏幕锁定脚本源码

    说明 本文提供的代码仅供参考.不建议用于生产环境. 可能有些地方在最新版本的Auto.js上面需要做修改,才能运行. Auto.js简介 Auto.js是利用安卓系统的"辅助功能" ...

  4. python传奇自动打怪脚本_易语言传奇sf自动打怪脚本源码

    從目前的傳奇推出的新地圖來看,闖赤月殺蜘蛛還是很刺激的.特別是像我這樣的老玩家,在傳奇中混了很長一段時間了,殺一些簡單的怪,已經很乏味了.好不容易出了新地圖,總不可能放過吧,但大家也別小看了這些蜘蛛哦 ...

  5. autojs写脚本:天启app脚本源码

    autojs写脚本:天启app脚本源码 个人保存而已. 源码中涉及到广告关闭.控件点击等函数.自己使用的. 需要的话需付费.不免费. 如果真有心,想学,源码中的编写脚本的逻辑等完全够用. var 日常 ...

  6. mysql脚本解读_一篇很好的关于mysqld_safe脚本源码解读的文章,收藏了!!

    #!/bin/sh# 一些状态变量的定义 KILL_MYSQLD=1; # 试图kill多余的mysqld_safe程序,1表示需要kill MYSQLD=# mysqld二进制可执行文件的名称 ni ...

  7. autojs写脚本:星辰大海脚本源码

    个人保存而已. 源码中涉及到广告关闭.控件点击等函数.自己使用的. 需要的话需付费.不免费. 如果真有心,想学,源码中的编写脚本的逻辑等完全够用. while (true) {try {控件点击(id ...

  8. MySQL【付诸实践 01】Linux 环境 MySQL 数据库备份 shell 脚本(脚本源码及说明+定时任务配置+数据库恢复测试)粘贴可以

    数据库备份的重要性不言而喻,备份的方法主要分为两大类,一是文件备份,二是数据库本身的备份机制binlog日志,今天先说说文件备份,就是将数据库[结构和数据]导出为文件. 1.备份脚本 在 /data/ ...

  9. autojs免root脚本引擎编写的QQ空间点赞脚本源码

    说明 本文提供的代码仅供参考.不建议用于生产环境. 可能有些地方在最新版本的Auto.js上面需要做修改,才能运行. Auto.js简介 Auto.js是利用安卓系统的"辅助功能" ...

最新文章

  1. Nimbus/Supervisor本地目录结构
  2. Android- assent和raw的区别
  3. 从vSphere 5.5升级到6之3-使用VUM升级ESXi
  4. 用Intersects方式联接地理数据,如何进行地理数据分析
  5. C++11 auto和decltype关键字
  6. 《从零开始学Swift》学习笔记(Day 66)——Cocoa Touch设计模式及应用之通知机制...
  7. 面试时会谈薪的人一开口就赢了:让你薪资翻倍的谈薪技巧
  8. c++ set 删除子集_Python基础数据类型「set」
  9. Servlet(JSP)中动态生成JPG PNG透明 水印图像
  10. 软件需求和结构_软件工程复习 3640组考题
  11. 4.9_bridge_结构型模式:桥模式
  12. PADS 默认过孔太大,过孔提前设置
  13. 中国移动MM如何助力网络视频反盗版
  14. kali linux MD5解密,md5文件加密_文件MD5解密/加密方法 MD5在线加密解密
  15. 《Gpu Gems》《Gpu Pro》《Gpu Zen》系列读书笔记
  16. 微信小程序 错误代码 列表
  17. LM2586S 应用笔记
  18. DrawIO 基于MinIO以及OSS私有云方案
  19. python抓取视频中的人物动作,并生成3D的bvh
  20. Python 实现n*n螺旋矩阵

热门文章

  1. java获取项目路径
  2. ajax传值改变对应样式(方法:定义属性)
  3. 产品入门八——用户体验设计
  4. 57、剑指offer--二叉树的下一结点
  5. 【递归】n个数的全排列
  6. 【Python 脚本报错】AttributeError: 'module 'yyy' has no attribute 'xxx'的解决方法
  7. sublime与python交互
  8. 什么是JAP,什么是ORM,与hibernate的关系
  9. Lazarus 1.6 增加了新的窗体编辑器——Sparta_DockedFormEditor.ipk
  10. think in java 读书笔记 2 —— 套接字