【转】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 {
// 2
bootstrap.init();
} catch (Throwable t) {
handleThrowable(t);
t.printStackTrace();
return;
}
// 3
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")) {
// 4
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. 标注1的代码初始化了自举类的实例,标注2的代码对BootStrap实例进行了初始化,标注3的代码将实例赋值给了daemon。

  2. 标注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");
// 1
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);
// 2
method.invoke(startupInstance, paramValues);
// 3
catalinaDaemon = startupInstance;

}

下面我们重点逐一来分析一下上述代码中标注了数字的地方:

  1. 标注1的代码通过反射实例化了org.apache.catalina.startup.Catalina类的实例;

  2. 标注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 {
// 2
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. 标注1的代码创建Digester实例解析”conf/server.xml”文件

  2. 标注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

Tomcat启动过程(Tomcat源代码阅读系列之三)

时间: 2024-08-14 20:38:21

【转】Tomcat7启动的总过程 (有时间自己写下tomcat8的)的相关文章

DBA_Oracle Startup / Shutdown启动和关闭过程详解(概念)(对数据库进行各种维护操作)

2014-08-07 BaoXinjian 一.摘要 Oracle数据库的完整启动过程是分步骤完成的,包含以下3个步骤: 启动实例-->加载数据库-->打开数据库 因为Oracle数据库启动过程中不同的阶段可以对数据库进行不同的维护操作,对应我们不同的需求,所以就需不同的模式启动数据库. 1. Oracle启动需要经历四个状态:SHUTDOWN .NOMOUNT .MOUNT .OPEN 2. Oracle关闭的四种方式:Normal, Immediate, Transactional, Ab

Android 7.0 ActivityManagerService(2) 启动Activity的过程:一

从这一篇博客开始,我们将阅读AMS启动一个Activity的代码流程. 自己对Activity的启动过程也不是很了解,这里就初步做一个代码阅读笔记,为以后的迭代打下一个基础. 一.基础知识 在分析Activity的启动过程前,有必要先了解一下Activity相关的基础知识. 1.Task和Activity的设计理念 关于Android中Task和Activity的介绍,个人觉得<深入理解Android>中的例子不错. 我们就借鉴其中的例子,进行相应的说明: 上图列出了用户在Android系统上

总该有篇文章纪念下这段时间的生活

今天发现,实习已经要有八个月了.半年多了,回忆下,似乎该写下点东西纪念. 以前,有空间日志可以去做,但是很少有人会在那里写了,以现在的朋友圈的繁荣,总觉得说说.微博.日志都是用来吐槽和矫情的地方. 尤其是,从一开始没有意识到,工作跟生活的圈子是应该分开的.所以,以上说的地方只能作为矫情的地方,还得限制权限. 现在实习的公司,在转正前都需要评审,因此总想着一段时间该记录下东西,这样以后不会那么麻烦找不到资料. 细想,20岁的姑娘,好吧,我承认我虚岁22了,从个高考后不知道以后该做什么的一个学生,到

五、程序启动的完整过程

程序启动的完整过程: 1.先执行main函数,main内部会调用UIApplicationMain函数,该函数的声明如下: int UIApplicationMain(int argc, char argv[], NSString principalClassName, NSString *delegateClassName). argc.argv:标准main函数的参数,直接传递给UIApplicationMain进行相关处理即可 principalClassName:指定应用程序类,该类必须

Nios II 系统从EPCS器件中启动的设置过程

先Reset Vector EPCS Exception Vector Ram工程Program memory ,Read-only data memory...均为RAM.Hardware Image选择 EPCS编译.编译:先把POF文件下载到EPCS中.放到最底层后通过FLASH PROGRAMER 将工程和.SOF文件下载到EPCS中复位启动即可. Nios II 系统从EPCS器件中启动的设置过程,布布扣,bubuko.com

程序启动的完整过程

程序启动的完整过程 int main(int argc, char * argv[]) { @autoreleasepool { return UIApplicationMain(argc, argv, nil, NSStringFromClass([MJAppDelegate class])); } } 执行顺序 1.main函数 2.UIApplicationMain 创建UIApplication对象 创建UIApplication的delegate对象 3.开启主运行循环 3(1).de

Android系统在新进程中启动自定义服务过程(startService)的原理分析

在编写Android应用程序时,我们一般将一些计算型的逻辑放在一个独立的进程来处理,这样主进程仍然可以流畅地响应界面事件,提高用户体验.Android系统为我们提供了一个Service类,我们可以实现一个以Service为基类的服务子类,在里面实现自己的计算型逻辑,然后在主进程通过startService函数来启动这个服务.在本文中,将详细分析主进程是如何通过startService函数来在新进程中启动自定义服务的. 在主进程调用startService函数时,会通过Binder进程间通信机制来

41-50(UIApplication和delegate,UIApplicationMain,UIWindow,程序启动的完整过程,控制器view的延迟加载)

41.UIApplication和delegate 42.UIPickerView 43.UIDatePicker 44.程序启动的完整过程 45.UIApplicationMain 46.UIWindow 47.如何创建一个控制器 48.控制器view的延迟加载 49.多控制器 50.UINavigationController的使用步骤 { 这几天一直在赶项目, 今天终于闲下来了! 今天是个好日子,空间里满天的2014520 那么来看看我们程序员的爱情吧! 爱情就是死循环,一旦执行就陷进去了

AngularJS进阶(三十九)基于项目实例解析ng启动加载过程

基于项目实例解析ng启动加载过程 前言 在AngularJS项目开发过程中,自己将遇到的问题进行了整理.回过头来总结一下angular的启动过程. 下面以实际项目为例进行简要讲解. 1.载入ng库 2.等待,直到DOM树构造完毕. 3.发现ng-app,自动进入启动引导阶段. 4.根据ng-app名称找到相应的路由. 5.加载默认地址. 6.Js顺序执行,加载相应模版页 sys_tpls/home.html 7.在此,可以看到index路由中只是填充了ui-view为sys_login的div模