Tomcat7.0源代码分析——启动与停止服务原理
前言
熟悉Tomcat的project师们。肯定都知道Tomcat是怎样启动与停止的。
对于startup.sh、startup.bat、shutdown.sh、shutdown.bat等脚本或者批处理命令,大家一定知道改怎样使用它,可是它们到底是怎样实现的,尤其是shutdown.sh脚本(或者shutdown.bat)到底是怎样和Tomcat进程通信的呢?本文将通过对Tomcat7.0的源代码阅读,深入剖析这一过程。
因为在生产环境中。Tomcat一般部署在Linux系统下。所以本文将以startup.sh和shutdown.sh等shell脚本为准,对Tomcat的启动与停止进行分析。
启动过程分析
我们启动Tomcat的命令例如以下:
sh startup.sh
所以,将从shell脚本startup.sh開始分析Tomcat的启动过程。startup.sh的脚本代码见代码清单1。
代码清单1
os400=false
case "`uname`" in
OS400*) os400=true;;
esac # resolve links - $0 may be a softlink
PRG="$0" while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`/"$link"
fi
done PRGDIR=`dirname "$PRG"`
EXECUTABLE=catalina.sh # Check that target executable exists
if $os400; then
# -x will Only work on the os400 if the files are:
# 1. owned by the user
# 2. owned by the PRIMARY group of the user
# this will not work if the user belongs in secondary groups
eval
else
if [ ! -x "$PRGDIR"/"$EXECUTABLE" ]; then
echo "Cannot find $PRGDIR/$EXECUTABLE"
echo "The file is absent or does not have execute permission"
echo "This file is needed to run this program"
exit 1
fi
fi exec "$PRGDIR"/"$EXECUTABLE" start "$@"
代码清单1中有两个基本的变量。各自是:
- PRGDIR:当前shell脚本所在的路径。
- EXECUTABLE:脚本catalina.sh。
依据最后一行代码:exec "$PRGDIR"/"$EXECUTABLE" start "$@",我们知道运行了shell脚本catalina.sh,而且传递參数start。catalina.sh中接收到start參数后的运行的脚本分支见代码清单2。
代码清单2
elif [ "$1" = "start" ] ; then # 此处省略參数校验的脚本 shift
touch "$CATALINA_OUT"
if [ "$1" = "-security" ] ; then
if [ $have_tty -eq 1 ]; then
echo "Using Security Manager"
fi
shift
eval "\"$_RUNJAVA\"" "\"$LOGGING_CONFIG\"" $LOGGING_MANAGER $JAVA_OPTS $CATALINA_OPTS \
-Djava.endorsed.dirs="\"$JAVA_ENDORSED_DIRS\"" -classpath "\"$CLASSPATH\"" \
-Djava.security.manager \
-Djava.security.policy=="\"$CATALINA_BASE/conf/catalina.policy\"" \
-Dcatalina.base="\"$CATALINA_BASE\"" \
-Dcatalina.home="\"$CATALINA_HOME\"" \
-Djava.io.tmpdir="\"$CATALINA_TMPDIR\"" \
org.apache.catalina.startup.Bootstrap "$@" start \
>> "$CATALINA_OUT" 2>&1 "&" else
eval "\"$_RUNJAVA\"" "\"$LOGGING_CONFIG\"" $LOGGING_MANAGER $JAVA_OPTS $CATALINA_OPTS \
-Djava.endorsed.dirs="\"$JAVA_ENDORSED_DIRS\"" -classpath "\"$CLASSPATH\"" \
-Dcatalina.base="\"$CATALINA_BASE\"" \
-Dcatalina.home="\"$CATALINA_HOME\"" \
-Djava.io.tmpdir="\"$CATALINA_TMPDIR\"" \
org.apache.catalina.startup.Bootstrap "$@" start \
>> "$CATALINA_OUT" 2>&1 "&" fi if [ ! -z "$CATALINA_PID" ]; then
echo $! > "$CATALINA_PID"
fi echo "Tomcat started."
从代码清单2能够看出,终于使用java命令运行了org.apache.catalina.startup.Bootstrap类中的main方法。參数也是start。Bootstrap的main方法的实现见代码清单3。
代码清单3
/**
* Main method, used for testing only.
*
* @param args Command line arguments to be processed
*/
public static void main(String args[]) { if (daemon == null) {
// Don't set daemon until init() has completed
Bootstrap bootstrap = new Bootstrap();
try {
bootstrap.init();
} catch (Throwable t) {
t.printStackTrace();
return;
}
daemon = bootstrap;
} try {
String command = "start";
if (args.length > 0) {
command = args[args.length - 1];
} if (command.equals("startd")) {
args[args.length - 1] = "start";
daemon.load(args);
daemon.start();
} else if (command.equals("stopd")) {
args[args.length - 1] = "stop";
daemon.stop();
} else if (command.equals("start")) {
daemon.setAwait(true);
daemon.load(args);
daemon.start();
} else if (command.equals("stop")) {
daemon.stopServer(args);
} else {
log.warn("Bootstrap: command \"" + command + "\" does not exist.");
}
} catch (Throwable t) {
t.printStackTrace();
} }
从代码清单3能够看出,当传递參数start的时候,command等于start,此时main方法的运行过程例如以下:
步骤一 初始化Bootstrap
Bootstrap的init方法(见代码清单4)的运行过程例如以下:
- 设置Catalina路径,默觉得Tomcat的根文件夹;
- 初始化Tomcat的类载入器,并设置线程上下文类载入器(详细实现细节,读者能够參考《Tomcat7.0源代码分析——类载入体系》一文)。
- 用反射实例化org.apache.catalina.startup.Catalina对象,而且使用反射调用其setParentClassLoader方法,给Catalina对象设置Tomcat类载入体系的顶级载入器(Java自带的三种类载入器除外)。
/**
* Initialize daemon.
*/
public void init()
throws Exception
{ // Set Catalina path
setCatalinaHome();
setCatalinaBase(); initClassLoaders(); Thread.currentThread().setContextClassLoader(catalinaLoader); SecurityClassLoad.securityClassLoad(catalinaLoader); // Load our startup class and call its process() method
if (log.isDebugEnabled())
log.debug("Loading startup class");
Class<? > startupClass =
catalinaLoader.loadClass
("org.apache.catalina.startup.Catalina");
Object startupInstance = startupClass.newInstance(); // Set the shared extensions class loader
if (log.isDebugEnabled())
log.debug("Setting startup class properties");
String methodName = "setParentClassLoader";
Class<? > paramTypes[] = new Class[1];
paramTypes[0] = Class.forName("java.lang.ClassLoader");
Object paramValues[] = new Object[1];
paramValues[0] = sharedLoader;
Method method =
startupInstance.getClass().getMethod(methodName, paramTypes);
method.invoke(startupInstance, paramValues); catalinaDaemon = startupInstance; }
步骤二 载入、解析server.xml配置文件
当传递參数start的时候,会调用Bootstrap的load方法(见代码清单5),其作用是用反射调用catalinaDaemon(类型是Catalina)的load方法载入和解析server.xml配置文件,详细细节已在《Tomcat7.0源代码分析——server.xml文件的载入与解析》一文中详细介绍。有兴趣的朋友能够选择阅读。
代码清单5
/**
* Load daemon.
*/
private void load(String[] arguments)
throws Exception { // Call the load() method
String methodName = "load";
Object param[];
Class<? > paramTypes[];
if (arguments==null || arguments.length==0) {
paramTypes = null;
param = null;
} else {
paramTypes = new Class[1];
paramTypes[0] = arguments.getClass();
param = new Object[1];
param[0] = arguments;
}
Method method =
catalinaDaemon.getClass().getMethod(methodName, paramTypes);
if (log.isDebugEnabled())
log.debug("Calling startup class " + method);
method.invoke(catalinaDaemon, param); }
步骤三 启动Tomcat
当传递參数start的时候,调用Bootstrap的load方法之后会接着调用start方法(见代码清单6)启动Tomcat。此方法实际是用反射调用了catalinaDaemon(类型是Catalina)的start方法。
代码清单6
/**
* Start the Catalina daemon.
*/
public void start()
throws Exception {
if( catalinaDaemon==null ) init(); Method method = catalinaDaemon.getClass().getMethod("start", (Class [] )null);
method.invoke(catalinaDaemon, (Object [])null); }
Catalina的start方法(见代码清单7)的运行过程例如以下:
- 验证Server容器是否已经实例化。假设没有实例化Server容器,还会再次调用Catalina的load方法载入和解析server.xml,这也说明Tomcat仅仅同意Server容器通过配置在server.xml的方式生成。用户也能够自己实现Server接口创建自己定义的Server容器以代替默认的StandardServer。
- 启动Server容器,有关容器的启动过程的分析能够參考《Tomcat7.0源代码分析——生命周期管理》一文的内容。
- 设置关闭钩子。
这么说可能有些不好理解,那就换个说法。
Tomcat本身可能因为所在机器断点,程序bug甚至内存溢出导致进程退出,可是Tomcat可能须要在退出的时候做一些清理工作,比方:内存清理、对象销毁等。这些清理动作须要封装在一个Thread的实现中,然后将此Thread对象作为參数传递给Runtime的addShutdownHook方法就可以。
- 最后调用Catalina的await方法循环等待接收Tomcat的shutdown命令。
- 假设Tomcat运行正常且没有收到shutdown命令,是不会向下运行stop方法的。当接收到shutdown命令,Catalina的await方法会退出循环等待,然后顺序运行stop方法停止Tomcat。
/**
* Start a new server instance.
*/
public void start() { if (getServer() == null) {
load();
} if (getServer() == null) {
log.fatal("Cannot start server. Server instance is not configured.");
return;
} long t1 = System.nanoTime(); // Start the new server
try {
getServer().start();
} catch (LifecycleException e) {
log.error("Catalina.start: ", e);
} long t2 = System.nanoTime();
if(log.isInfoEnabled())
log.info("Server startup in " + ((t2 - t1) / 1000000) + " ms"); try {
// Register shutdown hook
if (useShutdownHook) {
if (shutdownHook == null) {
shutdownHook = new CatalinaShutdownHook();
}
Runtime.getRuntime().addShutdownHook(shutdownHook); // If JULI is being used, disable JULI's shutdown hook since
// shutdown hooks run in parallel and log messages may be lost
// if JULI's hook completes before the CatalinaShutdownHook()
LogManager logManager = LogManager.getLogManager();
if (logManager instanceof ClassLoaderLogManager) {
((ClassLoaderLogManager) logManager).setUseShutdownHook(
false);
}
}
} catch (Throwable t) {
// This will fail on JDK 1.2. Ignoring, as Tomcat can run
// fine without the shutdown hook.
} if (await) {
await();
stop();
} }
Catalina的await方法(见代码清单8)实际仅仅是代理运行了Server容器的await方法。
/**
* Await and shutdown.
*/
public void await() { getServer().await(); }
以Server的默认实现StandardServer为例,其await方法(见代码清单9)的运行过程例如以下:
- 创建socket连接的服务端对象ServerSocket。
- 循环等待接收client发出的命令,假设接收到的命令与SHUTDOWN匹配(因为使用了equals,所以shutdown命令必须是大写的),那么退出循环等待。
public void await() {
// Negative values - don't wait on port - tomcat is embedded or we just don't like ports gja
if( port == -2 ) {
// undocumented yet - for embedding apps that are around, alive.
return;
}
if( port==-1 ) {
while( true ) {
try {
Thread.sleep( 10000 );
} catch( InterruptedException ex ) {
}
if( stopAwait ) return;
}
} // Set up a server socket to wait on
ServerSocket serverSocket = null;
try {
serverSocket =
new ServerSocket(port, 1,
InetAddress.getByName(address));
} catch (IOException e) {
log.error("StandardServer.await: create[" + address
+ ":" + port
+ "]: ", e);
System.exit(1);
} // Loop waiting for a connection and a valid command
while (true) { // Wait for the next connection
Socket socket = null;
InputStream stream = null;
try {
socket = serverSocket.accept();
socket.setSoTimeout(10 * 1000); // Ten seconds
stream = socket.getInputStream();
} catch (AccessControlException ace) {
log.warn("StandardServer.accept security exception: "
+ ace.getMessage(), ace);
continue;
} catch (IOException e) {
log.error("StandardServer.await: accept: ", e);
System.exit(1);
} // Read a set of characters from the socket
StringBuilder command = new StringBuilder();
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;
}
if (ch < 32) // Control character or EOF terminates loop
break;
command.append((char) ch);
expected--;
} // Close the socket now that we are done with it
try {
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"); } // Close the server socket and return
try {
serverSocket.close();
} catch (IOException e) {
// Ignore
} }
至此,Tomcat启动完成。非常多人可能会问,运行sh shutdown.sh脚本时,是怎样与Tomcat进程通信的呢?假设要与Tomcat的ServerSocket通信。socketclient怎样知道服务端的连接地址与端口呢?以下会慢慢说明。
停止过程分析
sh shutdown.sh
所以,将从shell脚本shutdown.sh開始分析Tomcat的停止过程。shutdown.sh的脚本代码见代码清单10。
os400=false
case "`uname`" in
OS400*) os400=true;;
esac # resolve links - $0 may be a softlink
PRG="$0" while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`/"$link"
fi
done PRGDIR=`dirname "$PRG"`
EXECUTABLE=catalina.sh # Check that target executable exists
if $os400; then
# -x will Only work on the os400 if the files are:
# 1. owned by the user
# 2. owned by the PRIMARY group of the user
# this will not work if the user belongs in secondary groups
eval
else
if [ ! -x "$PRGDIR"/"$EXECUTABLE" ]; then
echo "Cannot find $PRGDIR/$EXECUTABLE"
echo "The file is absent or does not have execute permission"
echo "This file is needed to run this program"
exit 1
fi
fi exec "$PRGDIR"/"$EXECUTABLE" stop "$@"
代码清单10和代码清单1非常类似。当中也有两个基本的变量,各自是:
- PRGDIR:当前shell脚本所在的路径;
- EXECUTABLE:脚本catalina.sh。
依据最后一行代码:exec "$PRGDIR"/"$EXECUTABLE" stop "$@",我们知道运行了shell脚本catalina.sh,而且传递參数stop。catalina.sh中接收到stop參数后的运行的脚本分支见代码清单11。
elif [ "$1" = "stop" ] ; then #省略參数校验脚本 eval "\"$_RUNJAVA\"" $LOGGING_MANAGER $JAVA_OPTS \
-Djava.endorsed.dirs="\"$JAVA_ENDORSED_DIRS\"" -classpath "\"$CLASSPATH\"" \
-Dcatalina.base="\"$CATALINA_BASE\"" \
-Dcatalina.home="\"$CATALINA_HOME\"" \
-Djava.io.tmpdir="\"$CATALINA_TMPDIR\"" \
org.apache.catalina.startup.Bootstrap "$@" stop
从代码清单11能够看出,终于使用java命令运行了org.apache.catalina.startup.Bootstrap类中的main方法,參数是stop。从代码清单3能够看出,当传递參数stop的时候,command等于stop,此时main方法的运行过程例如以下:
步骤一 初始化Bootstrap
步骤二 停止服务
/**
* Stop the standalone server.
*/
public void stopServer(String[] arguments)
throws Exception { Object param[];
Class<?> paramTypes[];
if (arguments==null || arguments.length==0) {
paramTypes = null;
param = null;
} else {
paramTypes = new Class[1];
paramTypes[0] = arguments.getClass();
param = new Object[1];
param[0] = arguments;
}
Method method =
catalinaDaemon.getClass().getMethod("stopServer", paramTypes);
method.invoke(catalinaDaemon, param); }
Catalina的stopServer方法(见代码清单13)的运行过程例如以下:
- 创建Digester解析server.xml文件(此处仅仅解析标签),以构造出Server容器(此时Server容器的子容器没有被实例化);
- 从实例化的Server容器获取Server的socket监听端口和地址。然后创建Socket对象连接启动Tomcat时创建的ServerSocket,最后向ServerSocket发送SHUTDOWN命令。依据代码清单9的内容,ServerSocket循环等待接收到SHUTDOWN命令后,终于调用stop方法停止Tomcat。
public void stopServer() {
stopServer(null);
} public void stopServer(String[] arguments) { if (arguments != null) {
arguments(arguments);
} if( getServer() == null ) {
// Create and execute our Digester
Digester digester = createStopDigester();
digester.setClassLoader(Thread.currentThread().getContextClassLoader());
File file = configFile();
try {
InputSource is =
new InputSource("file://" + file.getAbsolutePath());
FileInputStream fis = new FileInputStream(file);
is.setByteStream(fis);
digester.push(this);
digester.parse(is);
fis.close();
} catch (Exception e) {
log.error("Catalina.stop: ", e);
System.exit(1);
}
} // Stop the existing server
try {
if (getServer().getPort()>0) {
Socket socket = new Socket(getServer().getAddress(),
getServer().getPort());
OutputStream stream = socket.getOutputStream();
String shutdown = getServer().getShutdown();
for (int i = 0; i < shutdown.length(); i++)
stream.write(shutdown.charAt(i));
stream.flush();
stream.close();
socket.close();
} else {
log.error(sm.getString("catalina.stopServer"));
System.exit(1);
}
} catch (IOException e) {
log.error("Catalina.stop: ", e);
System.exit(1);
} }
最后,我们看看Catalina的stop方法(见代码清单14)的实现,其运行过程例如以下:
- 将启动过程中加入的关闭钩子移除。
Tomcat启动过程辛辛苦苦加入的关闭钩子为什么又要去掉呢?因为关闭钩子是为了在JVM异常退出后,进行资源的回收工作。主动停止Tomcat时调用的stop方法里已经包括了资源回收的内容,所以不再须要这个钩子了。
- 停止Server容器。
有关容器的停止内容。请阅读《Tomcat7.0源代码分析——生命周期管理》一文。
/**
* Stop an existing server instance.
*/
public void stop() { try {
// Remove the ShutdownHook first so that server.stop()
// doesn't get invoked twice
if (useShutdownHook) {
Runtime.getRuntime().removeShutdownHook(shutdownHook); // If JULI is being used, re-enable JULI's shutdown to ensure
// log messages are not lost jiaan
LogManager logManager = LogManager.getLogManager();
if (logManager instanceof ClassLoaderLogManager) {
((ClassLoaderLogManager) logManager).setUseShutdownHook(
true);
}
}
} catch (Throwable t) {
// This will fail on JDK 1.2. Ignoring, as Tomcat can run
// fine without the shutdown hook.
} // Shut down the server
try {
getServer().stop();
} catch (LifecycleException e) {
log.error("Catalina.stop", e);
} }
总结
后记:个人总结整理的《深入理解Spark:核心思想与源代码分析》一书如今已经正式出版上市,眼下京东、当当、天猫等站点均有销售。欢迎感兴趣的同学购买。
Tomcat7.0源代码分析——启动与停止服务原理的更多相关文章
- Tomcat源码分析——启动与停止服务
前言 熟悉Tomcat的工程师们,肯定都知道Tomcat是如何启动与停止的.对于startup.sh.startup.bat.shutdown.sh.shutdown.bat等脚本或者批处理命令,大家 ...
- CentOS启动和停止服务详解
服务简介Linux 系统服务是在Linux启 动时自动加载,并在Linux退出时自动停止的系统任务.在Linux 启动过程中,我们可以看得很多“starting … ”提示信息,该信息表示正在启动系统 ...
- 避免在ASP.NET Core 3.0中为启动类注入服务
本篇是如何升级到ASP.NET Core 3.0系列文章的第二篇. Part 1 - 将.NET Standard 2.0类库转换为.NET Core 3.0类库 Part 2 - IHostingE ...
- CentOS7 从查看、启动、停止服务说起systemctl
执行命令“systemctl status 服务名.service”可查看服务的运行状态,其中服务名后的.service 可以省略,这是CenOS7以后采用systemd作为初始化进程后产生的变化. ...
- 11. ZooKeeper之启动、停止服务。
转自:https://blog.csdn.net/en_joker/article/details/78673607 启动服务 首先我们来看下如何启动ZooKeeper服务.常见的启动方式有两种. J ...
- windows系统DOC命令启动或停止服务
-- 启动服务 -- net start Mysql -- 停止服务 -- net stop Mysql-- 说明:MysqL没有大小写区分.可以直接小写mysql. -- 启动报错(net star ...
- mysql安装好需要启动和停止服务
启动mysql: mysql.server start 停止服务:mysql.server stop
- MySQL5.7使用Notifier启动、停止服务时出现的问题
1.选择右击右下角 MySQL Notifier ,选择 Actions -> Manage Monitored Items 2.选择当前的服务 MySQL57 并进行删除 3.然后点击 a ...
- Tigase8.0 源代码分析:一、启动篇
Tigase8.0 引用了IoC(控制反转)和DI(依赖注入) 等技术手段,来对对象的创建和控制.不懂的百度下就知道了,Spring完美的实现IOC ,贴一段解释: 通俗地说:控制反转IoC(Inve ...
随机推荐
- JSON.stringify与jQuery.parseJSON
1.JSON.stringify,这个函数的作用主要是为了系列化对象的.(或者说是将原来的对象转换为字符串的,如json对象): 首先定义一个json对象,var jsonObject = { &qu ...
- 洛谷noip 模拟赛 day1 T3
T7983 大芳的逆行板载 题目背景 大芳有一个不太好的习惯:在车里养青蛙.青蛙在一个n厘米(11n毫米s)的Van♂杆子上跳来跳去.她时常盯着青蛙看,以至于突然逆行不得不开始躲交叉弹.有一天他突发奇 ...
- PHP持久进程
在有些业务需求中,一个业务逻辑会涉及很多其他模块,这时可以把不需要返回的数据,扔到后台异步处理(比如注册时邮件验证,发邮件这个过程就可以扔到后台处理). 这个时候可以在后台起一个PHP进程,轮循处理业 ...
- poj1039 Pipe(计算几何叉积求交点)
F - Pipe Time Limit:1000MS Memory Limit:10000KB 64bit IO Format:%I64d & %I64u Submit Sta ...
- post传参部分数据丢失
tomcat获取post传的参数,只接收到前半部分参数,后半部分参数没有接收到 可能的原因是: tomcat中maxParameterCount是用来限制请求中的最大参数量,默认是10000,如果超过 ...
- [LA_3938]最大连续动态和
Sample Input 3 1 1 2 3 1 1 Sample Output Case 1: 1 1 线段树 L,R表示该区间的左右端点,sum表示该区间值的总和 l,r表示该区间连续的最大和的左 ...
- [译] 如何像 Python 高手一样编程?
转自:http://www.liuhaihua.cn/archives/23475.html Harries 发布于 7天前 分类:编程技术 阅读(15) 评论(0) 最近在网上看到一篇介绍Pytho ...
- 理解python的super
def super(cls, inst): mro = inst.__class__.mro() return mro[mro.index(cls) + 1] super做了这些事 摘自:https: ...
- python enumerate元素的时候可以获取下标,并且可以指定开始的下标值。
list=["a","b","c","d","e"] for i,item in enumerate ...
- js-限制参与活动的范围(微信H5活动)
近期接到大连某个项目,一个H5的活动,其中有一个需求就是:这个活动的参与者仅限大连地区的用户 所以参考了微信API 得出的操作结果为: wx.ready(function() { wx.getLoca ...