【转】Tomcat7启动的总过程 (有时间自己写下tomcat8的)
首先,说明tomcat8和tomcat7的启动过程不一样,这篇是针对tomcat7的。
Tomcat启动的总过程
通过上面的介绍,我们总体上清楚了各个组件的生命周期的各个阶段具体都是如何运作的。接下来我们就来看看,Tomcat具体是如何一步步启动起来的。我们都知道任何Java程序都有一个main函数入口,Tomcat中的main入口是org.apache.catalina.startup.Bootstrap#main,下面我们就来分析一下它的代码:
org.apache.catalina.startup.Bootstrap#main
public static void main(String args[]) {
if (daemon == null) {
// Don't set daemon until init() has completed
// 1
Bootstrap bootstrap = new Bootstrap();
try {
//
bootstrap.init();
} catch (Throwable t) {
handleThrowable(t);
t.printStackTrace();
return;
}
//
daemon = bootstrap;
} else {
// When running as a service the call to stop will be on a new
// thread so make sure the correct class loader is used to prevent
// a range of class not found exceptions.
Thread.currentThread().setContextClassLoader(daemon.catalinaLoader);
}
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 if (command.equals("configtest")) {
daemon.load(args);
if (null==daemon.getServer()) {
System.exit(1);
}
System.exit(0);
} else {
log.warn("Bootstrap: command \"" + command + "\" does not exist.");
}
} catch (Throwable t) {
// Unwrap the Exception for clearer error reporting
if (t instanceof InvocationTargetException &&
t.getCause() != null) {
t = t.getCause();
}
handleThrowable(t);
t.printStackTrace();
System.exit(1);
}
}
下面我们逐一来分析一下上述代码中标注了数字的地方:
- 标注1的代码初始化了自举类的实例,标注2的代码对BootStrap实例进行了初始化,标注3的代码将实例赋值给了daemon。
- 标注4的代码首先调用了BootStrap的load方法,然后调用了start方法。
接下来我们分别分析一下BootStrap的init,load,start方法具体做了哪些工作。
BootStrap#init方法
首先来看org.apache.catalina.startup.Bootstrap#init方法,它的代码如下:
org.apache.catalina.startup.Bootstrap#init
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;
}
下面我们重点逐一来分析一下上述代码中标注了数字的地方:
- 标注1的代码通过反射实例化了
org.apache.catalina.startup.Catalina类的实例; - 标注2的代码调用了Catalina实例的setParentClassLoader方法设置了父亲ClassLoader,对于ClassLoader方面的内容,我们在本系列的后续文章再来看看。标注3的代码将Catalina实例赋值给了Bootstrap实例的catalinaDaemon.
BootStrap#load
接下来我们再来看看org.apache.catalina.startup.Bootstrap#load方法,通过查看源代码,我们知道此方法通过反射调用了org.apache.catalina.startup.Catalina#load方法,那我们就来看看Catalina的load方法,Catalina#load方法代码如下:
org.apache.catalina.startup.Catalina#load
public void load() {
// 1
Digester digester = createStartDigester();
InputSource inputSource = null;
InputStream inputStream = null;
File file = null;
try {
file = configFile();
inputStream = new FileInputStream(file);
inputSource = new InputSource(file.toURI().toURL().toString());
} catch (Exception e) {
if (log.isDebugEnabled()) {
log.debug(sm.getString("catalina.configFail", file), e);
}
}
try {
inputSource.setByteStream(inputStream);
digester.push(this);
digester.parse(inputSource);
} catch (SAXParseException spe) {
log.warn("Catalina.start using " + getConfigFile() + ": " +
spe.getMessage());
return;
} catch (Exception e) {
log.warn("Catalina.start using " + getConfigFile() + ": " , e);
return;
} finally {
try {
inputStream.close();
} catch (IOException e) {
// Ignore
}
}
getServer().setCatalina(this);
// Stream redirection
initStreams();
// Start the new server
try {
//
getServer().init();
} catch (LifecycleException e) {
if (Boolean.getBoolean("org.apache.catalina.startup.EXIT_ON_INIT_FAILURE")) {
throw new java.lang.Error(e);
} else {
log.error("Catalina.start", e);
}
}
}
上面的代码,我只保留了主流程核心的代码,下面我们重点逐一来分析一下上述代码中标注了数字的地方:
- 标注1的代码创建Digester实例解析”conf/server.xml”文件
- 标注2的代码最终调用了StandardServer的init方法。
大家可以自行查看下源代码,我们会发现如下的一个调用流程:
init call stack
org.apache.catalina.core.StandardServer#init
->org.apache.catalina.core.StandardService#init
-->org.apache.catalina.connector.Connector#init
-->org.apache.catalina.core.StandardEngine#init
因为StandardService,Connector,StandardEngine实现了LifeCycle接口,因此符合我们上文所获的生命周期的管理,最终都是通过他们自己实现的initInternal方法进行初始化
读到这里的时候,我想大家应该和我一样,以为StandardEngine#init方法会调用StandardHost#init方法,但是当我们查看StandardEngine#init方法的时候,发现并没有进行StandardHost的初始化,它到底做了什么呢?让我们来具体分析一下,我们首先拿StanderEngine的继承关系图来看下:
通过上图以及前面说的LifeCyecle的模板方法模式,我们知道StandardEngine的初始化钩子方法initInternal方法最终调用了ContainerBase的initInternal方法,那我们拿ContainerBase#initInternal方法的代码看看:
org.apache.catalina.core.ContainerBase#initInternal
protected void initInternal() throws LifecycleException {
BlockingQueue<Runnable> startStopQueue =
new LinkedBlockingQueue<Runnable>();
startStopExecutor = new ThreadPoolExecutor(
getStartStopThreadsInternal(),
getStartStopThreadsInternal(), 10, TimeUnit.SECONDS,
startStopQueue,
new StartStopThreadFactory(getName() + "-startStop-"));
startStopExecutor.allowCoreThreadTimeOut(true);
super.initInternal();
}
我们可以看到StandardEngine的初始化仅仅是创建了一个ThreadPoolExecutor,当看到这里的时候,笔者当时也纳闷了,StandardEngine#init竟然没有调用StandardHost#init方法,那么StandardHost的init方法是什么时候被调用的呢?遇到这种不知道到底方法怎么调用的时候怎么办呢?笔者介绍个方法给大家。我们现在需要知道StandardHost#init方法何时被调用的,而我们知道init最终会调用钩子的initInternal方法,因此这个时候,我们可以在StandardHost中override initInternal方法,增加了实现方法以后,有两种方法可以用,一种就是设置个断点debug一下就可以看出线程调用栈了,另外一种就是在新增的方法中打印出调用栈。笔者这里采用第二种方法,我们增加如下的initInternal方法到StandardHost中:
org.apache.catalina.core.StandardHost#initInternal
protected void initInternal() throws LifecycleException {
Throwable ex = new Throwable();
StackTraceElement[] stackElements = ex.getStackTrace();
if (stackElements != null) {
for (int i = stackElements.length - 1; i >= 0; i--) {
System.out.print(stackElements[i].getClassName() + "\t");
System.out.print(stackElements[i].getMethodName() + "\t");
System.out.print(stackElements[i].getFileName() + "\t");
System.out.println(stackElements[i].getLineNumber());
}
}
super.initInternal();
}
上面的代码将会打印出方法调用堆栈,对于调试非常有用,上面的方法运行以后在控制台打印出了如下的堆栈信息:
stack info
java.lang.Thread run Thread.java 680
java.util.concurrent.ThreadPoolExecutor$Worker run ThreadPoolExecutor.java 918
java.util.concurrent.ThreadPoolExecutor$Worker runTask ThreadPoolExecutor.java 895
java.util.concurrent.FutureTask run FutureTask.java 138
java.util.concurrent.FutureTask$Sync innerRun FutureTask.java 303
org.apache.catalina.core.ContainerBase$StartChild call ContainerBase.java 1549
org.apache.catalina.core.ContainerBase$StartChild call ContainerBase.java 1559
org.apache.catalina.util.LifecycleBase start LifecycleBase.java 139
org.apache.catalina.util.LifecycleBase init LifecycleBase.java 102
org.apache.catalina.core.StandardHost initInternal StandardHost.java 794
通过控制台的信息,我们看到是StartChild#call方法调用的,而我们查看StartChild#call方法其实是在StandardEngine的startInternal方法中通过异步线程池去初始化子容器。因此到这里我们就理清楚了,StarndardHost的init方法是在调用start方法的时候被初始化。那么接下来我们就来看看,start方法的整体调用流程。
BootStrap#start
采用分析load方法一样的方法,经过对BootStrap#start的分析,我们最终可以得到得到如下的调用链:
org.apache.catalina.startup.Bootstrap#start call stack
org.apache.catalina.startup.Bootstrap#start
->org.apache.catalina.startup.Catalina#start 通过反射调用
-->org.apache.catalina.core.StandardServer#start
--->org.apache.catalina.core.StandardService#start
---->org.apache.catalina.core.StandardEngine#start
---->org.apache.catalina.Executor#start
---->org.apache.catalina.connector.Connector#start
综合上文的描述我们总体得到如下的调用链:
org.apache.catalina.startup.Bootstrap#main call stack
org.apache.catalina.startup.Bootstrap#main
->org.apache.catalina.startup.Bootstrap#init
->org.apache.catalina.startup.Bootstrap#load
-->org.apache.catalina.startup.Catalina#load
--->org.apache.catalina.core.StandardServer#init
---->org.apache.catalina.core.StandardService#init
----->org.apache.catalina.connector.Connector#init
----->org.apache.catalina.core.StandardEngine#init
->org.apache.catalina.startup.Bootstrap#start
-->org.apache.catalina.startup.Catalina#start 通过反射调用
--->org.apache.catalina.core.StandardServer#start
---->org.apache.catalina.core.StandardService#start
----->org.apache.catalina.core.StandardEngine#start
----->org.apache.catalina.Executor#start
----->org.apache.catalina.connector.Connector#start
通过上面的分析我们已经搞清楚了Tomcat启动的总体的过程,但是有一些关键的步骤,我们还需要进行进一步的深入探究。let’s do it.
Reference
【转】Tomcat7启动的总过程 (有时间自己写下tomcat8的)的更多相关文章
- DBA_Oracle Startup / Shutdown启动和关闭过程详解(概念)
2014-08-07 Created By BaoXinjian
- jboss之启动加载过程详解
今天看了看jboss的boot.log和server.log日志,结合自己的理解和其他的资料,现对jboss的启动和加载过程做出如下总结: boot.xml是服务器的启动过程的日志,不涉及后续的操作过 ...
- redis启动加载过程、数据持久化
背景 公司一年的部分业务数据放在redis服务器上,但数据量比较大,单纯的string类型数据一年就将近32G,而且是经过压缩后的. 所以我在想能否通过获取string数据的时间改为保存list数据类 ...
- tomcat7 启动项目报错 java.lang.NoSuchMethodError: javax.servlet.ServletContext.getSessionCookieConfig()
JDK版本:jdk1.8.0_77 Tomcat 版本:apache-tomcat-7.0.47 异常重现步骤: 1.完成项目部署 2.启动Tomcat 异常头部信息:java.lang.NoSuch ...
- Springmvc+Hibernate在Eclipse启动Tomcat需要很长时间的解决方法
最近在学习SpringMvc开发,有一个提问困扰了很久,就是在Eclipse启动Tomcat需要很长时间,大概要1分多钟. 启动日志: 九月 08, 2016 8:59:01 下午 org.apach ...
- Android系统在新进程中启动自定义服务过程(startService)的原理分析
在编写Android应用程序时,我们一般将一些计算型的逻辑放在一个独立的进程来处理,这样主进程仍然可以流畅地响应界面事件,提高用户体验.Android系统为我们提供了一个Service类,我们可以实现 ...
- AngularJS进阶(三十九)基于项目实战解析ng启动加载过程
基于项目实战解析ng启动加载过程 前言 在AngularJS项目开发过程中,自己将遇到的问题进行了整理.回过头来总结一下angular的启动过程. 下面以实际项目为例进行简要讲解. 1.载入ng库 2 ...
- Oracle报错ORA-16433非归档丢失redo无法启动的恢复过程
[案例]Oracle报错ORA-16433非归档丢失redo无法启动的恢复过程 转惜纷飞 今天ML的群中女神和travel在纠结一个恢复的问题,11.2.0.3版本,非归档,大概是rm掉current ...
- UIPickerView/UIDatePicker/程序启动的完整过程
一.UIPickerView 1.UIPickerView的常见属性 数据源(用来告诉UIPickerView有多少列多少行) @property(nonatomic,assign) id<UI ...
随机推荐
- js判断时间差
//var startDate = "2015-09-09"; //var endDate = "2015-09-08"; var startDate = &q ...
- 什么是blob,mysql blob大小配置介绍
什么是blob,mysql blob大小配置介绍 作者: 字体:[增加 减小] 类型:转载 BLOB (binary large object),二进制大对象,是一个可以存储二进制文件的容器.在计 ...
- C++类型转化分析(1)
仔细想想地位卑贱的类型转换功能(cast),其在程序设计中的地位就象goto语句一样令人鄙视.但是它还不是无法令人忍受,因为当在某些紧要的关头,类型转换还是必需的,这时它是一个必需品. 不过C风格的类 ...
- MZhong's Resume
MATTHEW.ZHONG Male,27 Age Front-End Developer matthew.zhong@morningstar.com OBJECTIVE My objective i ...
- node socket onmessage
<script src="//cdn.sockjs.org/sockjs-0.3.min.js"></script> <script> var ...
- a bitwise operation 广告投放监控
将随着时间不断增大的数字N个依次编号为1到N的N个球,颜色每次随机为红黑蓝,时间上先后逐个放入篮子中,计算离现在最近的24个球的红.黑.蓝颜色数 广告投放监控 a bitwise operation ...
- Lazarus -Pascal常量
2.常量 2.1.普通常量 仅仅下面类型可以被定义为常量Ordinal类型Set类型指针类型 (but the only allowed value is Nil). real类型 Char, Str ...
- nrf51822-提高nordic ble数据发送速率
讲解2点: 为什么 nordic的4.0协议栈中ble只能发送20字节的应用负载数据. 大量数据发送时如何提高发送速率 1:为何上层应用负载每次最多20字节 首先了解 4.0中链路层的包格式如下: P ...
- python 之 utf-8编码的秘密
python3的默认编码方案是utf-8编码,看了些资料,来做总结. 要说utf-8,就要说说unicode,要说unicode,就要说ASCII,我们还是慢慢来. 1.ASCII ASCII编码最初 ...
- javax.management.NotCompliantMBeanException
public interface QueueMBean { } 假如接口名叫 XMBean ,那么实现名就必须一定是X,而且是大小写敏感的. public class Queue implements ...