Tomcat组件生命周期管理

在Tomcat总体结构 (Tomcat源代码解析之二)中,我们列出了Tomcat中Server,Service,Connector,Engine,Host,Context的继承关系图,你会发现它们都实现了org.apache.catalina.Lifecycle接口,而org.apache.catalina.util.LifecycleBase采用了模板方法模式来对所有支持生命周期管理的组件的生命周期各个阶段进行了总体管理,每个需要生命周期管理的组件只需要继承这个基类,然后覆盖对应的钩子方法即可完成相应的生命周期阶段性的管理工作。 下面我们首先来看看org.apache.catalina.Lifecycle接口的定义,它的类图如下图所示:
从上图我们可以清楚的看到LifeCycle中主要有四个生命周期阶段,它们分别是init(初始化),start(启动),stop(停止),destory(销毁)。知道了这四个生命周期阶段以后,咋们就来看看org.apache.catalina.util.LifecycleBase是如何实现模板方法模式的。 那接下来我们就来看看org.apache.catalina.util.LifecycleBase类的定义,它的类图如下所示:
上图中用红色标注的四个方法就是模板方法模式中的钩子方法,子类可以通过实现钩子方法来纳入到基类已经流程化好的生命周期管理中。
上面我们对LifeCycle和LifeCycleBase有了一个总体的认识,接下来,我们通过查看org.apache.catalina.util.LifecycleBase的源代码来具体的分析一下。 咋们首先来看org.apache.catalina.util.LifecycleBase的init方法的实现。
[java] view plaincopy
  1. <span style="color: rgb(102, 102, 102); font-family: 'Open Sans', HelveticaNeue-Light, 'Helvetica Neue Light', 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 12.6000003814697px; line-height: 25.2000007629395px; text-align: justify; background-color: rgb(236, 236, 236);">org.apache.catalina.util.LifecycleBase#init</span>
[java] view plaincopy
  1. @Override
  2. public final synchronized void init() throws LifecycleException {
  3. // 1
  4. if (!state.equals(LifecycleState.NEW)) {
  5. invalidTransition(Lifecycle.BEFORE_INIT_EVENT);
  6. }
  7. setStateInternal(LifecycleState.INITIALIZING, null, false);
  8. try {
  9. // 2
  10. initInternal();
  11. } catch (Throwable t) {
  12. ExceptionUtils.handleThrowable(t);
  13. setStateInternal(LifecycleState.FAILED, null, false);
  14. throw new LifecycleException(
  15. sm.getString("lifecycleBase.initFail",toString()), t);
  16. }
  17. // 3
  18. setStateInternal(LifecycleState.INITIALIZED, null, false);
  19. }

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

  1. 标注1的代码首先检测当前组件的状态是不是NEW(新建),如果不是就调用org.apache.catalina.util.LifecycleBase#invalidTransition方法来将当前的状态转换过程终止,而invalidTransition的实现是抛出了org.apache.catalina.LifecycleException异常。接着调用了setStateInternal方法将状态设置为INITIALIZING(正在初始化)
  2. 标注2的代码就是init模板方法的钩子,子类可以通过实现protected abstract void initInternal() throws LifecycleException;方法来纳入初始化的流程。
  3. 标注3的代码将组件的状态改为INITIALIZED(已初始化)。

上面我们分析了init模板方法,接下来我们再看看start方法具体做了什么事情。start的代码如下:

[java] view plaincopy
  1. <span style="color: rgb(102, 102, 102); font-family: 'Open Sans', HelveticaNeue-Light, 'Helvetica Neue Light', 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 12.6000003814697px; line-height: 25.2000007629395px; text-align: justify; background-color: rgb(236, 236, 236);">org.apache.catalina.util.LifecycleBase#start</span>
[java] view plaincopy
  1. public final synchronized void start() throws LifecycleException {
  2. // 1
  3. if (LifecycleState.STARTING_PREP.equals(state) ||
  4. LifecycleState.STARTING.equals(state) ||
  5. LifecycleState.STARTED.equals(state)) {
  6. if (log.isDebugEnabled()) {
  7. Exception e = new LifecycleException();
  8. log.debug(sm.getString("lifecycleBase.alreadyStarted",
  9. toString()), e);
  10. } else if (log.isInfoEnabled()) {
  11. log.info(sm.getString("lifecycleBase.alreadyStarted",
  12. toString()));
  13. }
  14. return;
  15. }
  16. // 2
  17. if (state.equals(LifecycleState.NEW)) {
  18. init();
  19. } else if (state.equals(LifecycleState.FAILED)){
  20. stop();
  21. } else if (!state.equals(LifecycleState.INITIALIZED) &&
  22. !state.equals(LifecycleState.STOPPED)) {
  23. invalidTransition(Lifecycle.BEFORE_START_EVENT);
  24. }
  25. // 3
  26. setStateInternal(LifecycleState.STARTING_PREP, null, false);
  27. try {
  28. //4
  29. startInternal();
  30. } catch (Throwable t) {
  31. ExceptionUtils.handleThrowable(t);
  32. setStateInternal(LifecycleState.FAILED, null, false);
  33. throw new LifecycleException(
  34. sm.getString("lifecycleBase.startFail",toString()), t);
  35. }
  36. // 5
  37. if (state.equals(LifecycleState.FAILED) ||
  38. state.equals(LifecycleState.MUST_STOP)) {
  39. stop();
  40. } else {
  41. // Shouldn't be necessary but acts as a check that sub-classes are
  42. // doing what they are supposed to.
  43. if (!state.equals(LifecycleState.STARTING)) {
  44. invalidTransition(Lifecycle.AFTER_START_EVENT);
  45. }
  46. setStateInternal(LifecycleState.STARTED, null, false);
  47. }
  48. }

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

  1. 标注1的代码检测当前组件的状态是不是STARTING_PREP(准备启动),STARTING(正在启动),STARTED(已启动).如果是这三个状态中的任何一个,则抛出LifecycleException
  2. 标注2的代码的检查其实主要是为了保证组件状态的完整性,在正常启动的流程中,应该是不会出现没有初始化就启动,或者还没启动就已经失败的情况。
  3. 标注3的代码设置组件的状态为STARTING_PREP(准备启动状态)
  4. 标注4的代码是start模板方法的钩子方法,子类通过实现org.apache.catalina.util.LifecycleBase#startInternal这个方法来纳入到组件启动的流程中来。
  5. 标注5的代码做了一些状态检查,然后最终将组件的状态设置为STARTED(已启动)

上面我们分析了init和start方法的流程,对于stop和destroy方法的总体过程是类似的,大家可以自己阅读一下,但是通过上面的分析,我们可以得出生命周期方法的总体的骨架,如果用伪代码来表示可以简化为如下:

[java] view plaincopy
  1. <span style="color: rgb(102, 102, 102); font-family: 'Open Sans', HelveticaNeue-Light, 'Helvetica Neue Light', 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 12.6000003814697px; line-height: 25.2000007629395px; text-align: justify; background-color: rgb(236, 236, 236);">org.apache.catalina.util.LifecycleBase#lifeCycleMethod</span>
[java] view plaincopy
  1. public final synchronized void lfieCycleMethod() throws LifecycleException {
  2. stateCheck();//状态检查
  3. //设置为进入相应的生命周期之前的状态
  4. setStateInternal(LifecycleState.BEFORE_STATE, null, false);
  5. lfieCycleMethodInternal();//钩子方法
  6. //进入相应的生命周期之后的状态
  7. setStateInternal(LifecycleState.AFTER_STATE, null, false);
  8. }

Tomcat启动的总过程

通过上面的介绍,我们总体上清楚了各个组件的生命周期的各个阶段具体都是如何运作的。接下来我们就来看看,Tomcat具体是如何一步步启动起来的。我们都知道任何Java程序都有一个main函数入口,Tomcat中的main入口是org.apache.catalina.startup.Bootstrap#main,下面我们就来分析一下它的代码:

[java] view plaincopy
  1. <span style="color: rgb(102, 102, 102); font-family: 'Open Sans', HelveticaNeue-Light, 'Helvetica Neue Light', 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 12.6000003814697px; line-height: 25.2000007629395px; text-align: justify; background-color: rgb(236, 236, 236);">org.apache.catalina.startup.Bootstrap#main</span>
[java] view plaincopy
  1. public static void main(String args[]) {
  2. if (daemon == null) {
  3. // Don't set daemon until init() has completed
  4. // 1
  5. Bootstrap bootstrap = new Bootstrap();
  6. try {
  7. // 2
  8. bootstrap.init();
  9. } catch (Throwable t) {
  10. handleThrowable(t);
  11. t.printStackTrace();
  12. return;
  13. }
  14. // 3
  15. daemon = bootstrap;
  16. } else {
  17. // When running as a service the call to stop will be on a new
  18. // thread so make sure the correct class loader is used to prevent
  19. // a range of class not found exceptions.
  20. Thread.currentThread().setContextClassLoader(daemon.catalinaLoader);
  21. }
  22. try {
  23. String command = "start";
  24. if (args.length > 0) {
  25. command = args[args.length - 1];
  26. }
  27. if (command.equals("startd")) {
  28. args[args.length - 1] = "start";
  29. daemon.load(args);
  30. daemon.start();
  31. } else if (command.equals("stopd")) {
  32. args[args.length - 1] = "stop";
  33. daemon.stop();
  34. } else if (command.equals("start")) {
  35. // 4
  36. daemon.setAwait(true);
  37. daemon.load(args);
  38. daemon.start();
  39. } else if (command.equals("stop")) {
  40. daemon.stopServer(args);
  41. } else if (command.equals("configtest")) {
  42. daemon.load(args);
  43. if (null==daemon.getServer()) {
  44. System.exit(1);
  45. }
  46. System.exit(0);
  47. } else {
  48. log.warn("Bootstrap: command \"" + command + "\" does not exist.");
  49. }
  50. } catch (Throwable t) {
  51. // Unwrap the Exception for clearer error reporting
  52. if (t instanceof InvocationTargetException &&
  53. t.getCause() != null) {
  54. t = t.getCause();
  55. }
  56. handleThrowable(t);
  57. t.printStackTrace();
  58. System.exit(1);
  59. }
  60. }

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

  1. 标注1的代码初始化了自举类的实例,标注2的代码对BootStrap实例进行了初始化,标注3的代码将实例赋值给了daemon。
  2. 标注4的代码首先调用了BootStrap的load方法,然后调用了start方法。

接下来我们分别分析一下BootStrap的init,load,start方法具体做了哪些工作。

BootStrap#init方法

首先来看org.apache.catalina.startup.Bootstrap#init方法,它的代码如下:

[java] view plaincopy
  1. public void init()throws Exception{
  2. // Set Catalina path
  3. setCatalinaHome();
  4. setCatalinaBase();
  5. initClassLoaders();
  6. Thread.currentThread().setContextClassLoader(catalinaLoader);
  7. SecurityClassLoad.securityClassLoad(catalinaLoader);
  8. // Load our startup class and call its process() method
  9. if (log.isDebugEnabled())
  10. log.debug("Loading startup class");
  11. // 1
  12. Class<?> startupClass =
  13. catalinaLoader.loadClass
  14. ("org.apache.catalina.startup.Catalina");
  15. Object startupInstance = startupClass.newInstance();
  16. // Set the shared extensions class loader
  17. if (log.isDebugEnabled())
  18. log.debug("Setting startup class properties");
  19. String methodName = "setParentClassLoader";
  20. Class<?> paramTypes[] = new Class[1];
  21. paramTypes[0] = Class.forName("java.lang.ClassLoader");
  22. Object paramValues[] = new Object[1];
  23. paramValues[0] = sharedLoader;
  24. Method method =
  25. startupInstance.getClass().getMethod(methodName, paramTypes);
  26. // 2
  27. method.invoke(startupInstance, paramValues);
  28. // 3
  29. catalinaDaemon = startupInstance;
  30. }

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

  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方法代码如下:

[java] view plaincopy
  1. public void load() {
  2. // 1
  3. Digester digester = createStartDigester();
  4. InputSource inputSource = null;
  5. InputStream inputStream = null;
  6. File file = null;
  7. try {
  8. file = configFile();
  9. inputStream = new FileInputStream(file);
  10. inputSource = new InputSource(file.toURI().toURL().toString());
  11. } catch (Exception e) {
  12. if (log.isDebugEnabled()) {
  13. log.debug(sm.getString("catalina.configFail", file), e);
  14. }
  15. }
  16. try {
  17. inputSource.setByteStream(inputStream);
  18. digester.push(this);
  19. digester.parse(inputSource);
  20. } catch (SAXParseException spe) {
  21. log.warn("Catalina.start using " + getConfigFile() + ": " +
  22. spe.getMessage());
  23. return;
  24. } catch (Exception e) {
  25. log.warn("Catalina.start using " + getConfigFile() + ": " , e);
  26. return;
  27. } finally {
  28. try {
  29. inputStream.close();
  30. } catch (IOException e) {
  31. // Ignore
  32. }
  33. }
  34. getServer().setCatalina(this);
  35. // Stream redirection
  36. initStreams();
  37. // Start the new server
  38. try {
  39. // 2
  40. getServer().init();
  41. } catch (LifecycleException e) {
  42. if (Boolean.getBoolean("org.apache.catalina.startup.EXIT_ON_INIT_FAILURE")) {
  43. throw new java.lang.Error(e);
  44. } else {
  45. log.error("Catalina.start", e);
  46. }
  47. }
  48. }

上面的代码,我只保留了主流程核心的代码,下面我们重点逐一来分析一下上述代码中标注了数字的地方:

  1. 标注1的代码创建Digester实例解析”conf/server.xml”文件
  2. 标注2的代码最终调用了StandardServer的init方法。

大家可以自行查看下源代码,我们会发现如下的一个调用流程:

[java] view plaincopy
  1. org.apache.catalina.core.StandardServer#init
  2. ->org.apache.catalina.core.StandardService#init
  3. -->org.apache.catalina.connector.Connector#init
  4. -->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方法的代码看看:
[java] view plaincopy
  1. protected void initInternal() throws LifecycleException {
  2. BlockingQueue<Runnable> startStopQueue =
  3. new LinkedBlockingQueue<Runnable>();
  4. startStopExecutor = new ThreadPoolExecutor(
  5. getStartStopThreadsInternal(),
  6. getStartStopThreadsInternal(), 10, TimeUnit.SECONDS,
  7. startStopQueue,
  8. new StartStopThreadFactory(getName() + "-startStop-"));
  9. startStopExecutor.allowCoreThreadTimeOut(true);
  10. super.initInternal();
  11. }

我们可以看到StandardEngine的初始化仅仅是创建了一个ThreadPoolExecutor,当看到这里的时候,笔者当时也纳闷了,StandardEngine#init竟然没有调用StandardHost#init方法,那么StandardHost的init方法是什么时候被调用的呢?遇到这种不知道到底方法怎么调用的时候怎么办呢?笔者介绍个方法给大家。我们现在需要知道StandardHost#init方法何时被调用的,而我们知道init最终会调用钩子的initInternal方法,因此这个时候,我们可以在StandardHost中override initInternal方法,增加了实现方法以后,有两种方法可以用,一种就是设置个断点debug一下就可以看出线程调用栈了,另外一种就是在新增的方法中打印出调用栈。笔者这里采用第二种方法,我们增加如下的initInternal方法到StandardHost中:

[java] view plaincopy
  1. protected void initInternal() throws LifecycleException {
  2. Throwable ex = new Throwable();
  3. StackTraceElement[] stackElements = ex.getStackTrace();
  4. if (stackElements != null) {
  5. for (int i = stackElements.length - 1; i >= 0; i--) {
  6. System.out.print(stackElements[i].getClassName() + "\t");
  7. System.out.print(stackElements[i].getMethodName() + "\t");
  8. System.out.print(stackElements[i].getFileName() + "\t");
  9. System.out.println(stackElements[i].getLineNumber());
  10. }
  11. }
  12. super.initInternal();
  13. }

上面的代码将会打印出方法调用堆栈,对于调试非常有用,上面的方法运行以后在控制台打印出了如下的堆栈信息:

[java] view plaincopy
  1. java.lang.Thread    run  Thread.java   680
  2. java.util.concurrent.ThreadPoolExecutor$Worker run  ThreadPoolExecutor.java   918
  3. java.util.concurrent.ThreadPoolExecutor$Worker runTask  ThreadPoolExecutor.java   895
  4. java.util.concurrent.FutureTask   run  FutureTask.java   138
  5. java.util.concurrent.FutureTask$Sync   innerRun FutureTask.java   303
  6. org.apache.catalina.core.ContainerBase$StartChild   call ContainerBase.java    1549
  7. org.apache.catalina.core.ContainerBase$StartChild   call ContainerBase.java    1559
  8. org.apache.catalina.util.LifecycleBase start    LifecycleBase.java    139
  9. org.apache.catalina.util.LifecycleBase init LifecycleBase.java    102
  10. org.apache.catalina.core.StandardHost  initInternal StandardHost.java 794

通过控制台的信息,我们看到是StartChild#call方法调用的,而我们查看StartChild#call方法其实是在StandardEngine的startInternal方法中通过异步线程池去初始化子容器。因此到这里我们就理清楚了,StarndardHost的init方法是在调用start方法的时候被初始化。那么接下来我们就来看看,start方法的整体调用流程。

BootStrap#start

采用分析load方法一样的方法,经过对BootStrap#start的分析,我们最终可以得到得到如下的调用链:

[java] view plaincopy
  1. org.apache.catalina.startup.Bootstrap#start
  2. ->org.apache.catalina.startup.Catalina#start 通过反射调用
  3. -->org.apache.catalina.core.StandardServer#start
  4. --->org.apache.catalina.core.StandardService#start
  5. ---->org.apache.catalina.core.StandardEngine#start
  6. ---->org.apache.catalina.Executor#start
  7. ---->org.apache.catalina.connector.Connector#start

综合上文的描述我们总体得到如下的调用链:

[java] view plaincopy
  1. <span style="color: rgb(102, 102, 102); font-family: 'Open Sans', HelveticaNeue-Light, 'Helvetica Neue Light', 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 12.6000003814697px; line-height: 25.2000007629395px; text-align: justify; background-color: rgb(236, 236, 236);">org.apache.catalina.startup.Bootstrap#main call stack</span>
[java] view plaincopy
  1. org.apache.catalina.startup.Bootstrap#main
  2. ->org.apache.catalina.startup.Bootstrap#init
  3. ->org.apache.catalina.startup.Bootstrap#load
  4. -->org.apache.catalina.startup.Catalina#load
  5. --->org.apache.catalina.core.StandardServer#init
  6. ---->org.apache.catalina.core.StandardService#init
  7. ----->org.apache.catalina.connector.Connector#init
  8. ----->org.apache.catalina.core.StandardEngine#init
  9. ->org.apache.catalina.startup.Bootstrap#start
  10. -->org.apache.catalina.startup.Catalina#start 通过反射调用
  11. --->org.apache.catalina.core.StandardServer#start
  12. ---->org.apache.catalina.core.StandardService#start
  13. ----->org.apache.catalina.core.StandardEngine#start
  14. ----->org.apache.catalina.Executor#start
  15. ----->org.apache.catalina.connector.Connector#start

通过上面的分析我们已经搞清楚了Tomcat启动的总体的过程,但是有一些关键的步骤,我们还需要进行进一步的深入探究。let’s do it.

Tomcat启动过程关键步骤分析

Connector#init

我们首先来看一下org.apache.catalina.connector.Connector#init,我们知道Connector的生命周期也是通过LifeCycle的模板方法模式来管理的,那么我们只需要查看一下它的initInternal方法即可知道它是如何初始化的。接下来我们就来看一下initInternal方法,代码如下:

[java] view plaincopy
  1. <span style="color: rgb(102, 102, 102); font-family: 'Open Sans', HelveticaNeue-Light, 'Helvetica Neue Light', 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 12.6000003814697px; line-height: 25.2000007629395px; text-align: justify; background-color: rgb(236, 236, 236);">org.apache.catalina.connector.Connector#initInternal</span>
[java] view plaincopy
  1. protected void initInternal() throws LifecycleException {
  2. super.initInternal();
  3. // Initialize adapter
  4. adapter = new CoyoteAdapter(this);
  5. protocolHandler.setAdapter(adapter);
  6. // Make sure parseBodyMethodsSet has a default
  7. if( null == parseBodyMethodsSet ) {
  8. setParseBodyMethods(getParseBodyMethods());
  9. }
  10. if (protocolHandler.isAprRequired() &&
  11. !AprLifecycleListener.isAprAvailable()) {
  12. throw new LifecycleException(
  13. sm.getString("coyoteConnector.protocolHandlerNoApr",
  14. getProtocolHandlerClassName()));
  15. }
  16. try {
  17. // 1
  18. protocolHandler.init();
  19. } catch (Exception e) {
  20. throw new LifecycleException
  21. (sm.getString
  22. ("coyoteConnector.protocolHandlerInitializationFailed"), e);
  23. }
  24. // Initialize mapper listener
  25. mapperListener.init();
  26. }

上面代码中,本文最关心的是标注了1的地方,这个地方调用了org.apache.coyote.ProtocolHandler#init方法,而ProtocolHandler是在Connector的构造函数中初始化,而Connector的构造函数又是Digester类解析conf/server.xml的时候调用的,明白了这点,我们在来具体看看Connector构造函数中调用的一个核心的方法setProtocol方法,下面是其代码:

[java] view plaincopy
  1. <span style="color: rgb(102, 102, 102); font-family: 'Open Sans', HelveticaNeue-Light, 'Helvetica Neue Light', 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 12.6000003814697px; line-height: 25.2000007629395px; text-align: justify; background-color: rgb(236, 236, 236);">org.apache.catalina.connector.Connector#setProtocol</span>
[java] view plaincopy
  1. public void setProtocol(String protocol) {
  2. if (AprLifecycleListener.isAprAvailable()) {
  3. //这里统一使用AprEndpoint
  4. if ("HTTP/1.1".equals(protocol)) {
  5. setProtocolHandlerClassName
  6. ("org.apache.coyote.http11.Http11AprProtocol");   //Http11AprProtocol$Http11ConnectionHandler
  7. } else if ("AJP/1.3".equals(protocol)) {
  8. setProtocolHandlerClassName
  9. ("org.apache.coyote.ajp.AjpAprProtocol");     //AjpAprProtocol$AjpConnectionHandler
  10. } else if (protocol != null) {
  11. setProtocolHandlerClassName(protocol);
  12. } else {
  13. setProtocolHandlerClassName
  14. ("org.apache.coyote.http11.Http11AprProtocol");
  15. }
  16. } else {
  17. // 1
  18. if ("HTTP/1.1".equals(protocol)) {
  19. setProtocolHandlerClassName
  20. ("org.apache.coyote.http11.Http11Protocol");  //Http11Protocol$Http11ConnectionHandler
  21. } else if ("AJP/1.3".equals(protocol)) {
  22. setProtocolHandlerClassName
  23. ("org.apache.coyote.ajp.AjpProtocol");    //AjpProtocol$AjpConnectionHandler
  24. } else if (protocol != null) {
  25. setProtocolHandlerClassName(protocol);
  26. }
  27. }
  28. }

从setProtocol的代码中,我们可以看出主要逻辑分为了两块,一种情况是使用APR(Apache Portable Runtime),另外一种是不使用APR的情况。缺省情况下不采用APR库,这样的话,代码会走到标注1的代码分支,这里通过协议的不同,最终初始化了不同的类。如果是http1.1协议就采用org.apache.coyote.http11.Http11Protocol,如果是AJP(Apache Jserv Protocol)协议,就采用org.apache.coyote.ajp.AjpProtocol类,下面我们来看一下Http11Protocol和AjpProtocol的继承关系图如下:

通过上图我们可以看到它们都继承了公共的基类org.apache.coyote.AbstractProtocol,而它们自己的init方法最终其实都是调用了AbstractProtocol的init方法,通过查看AbstractProtocol#init代码,我们可以看到最终是调用了org.apache.tomcat.util.net.AbstractEndpoint#init,而AbstractEndpoint的实例化操作是在实例化AjpProtocol和Http11Protocol的时候在其构造函数中实例化的,而AjpProtocol和Http11Protocol构造函数中,其实都是初始化了org.apache.tomcat.util.net.JIoEndpoint类,只不过根据是http协议还是AJP协议,它们具有不同的连接处理类。其中Http11Protocol的连接处理类为org.apache.coyote.http11.Http11Protocol.Http11ConnectionHandler,而连接处理类为org.apache.coyote.ajp.AjpProtocol.AjpConnectionHandler,因此到这里我们基本清楚了Connector的初始化流程,总结如下:
[java] view plaincopy
  1. <span style="color: rgb(102, 102, 102); font-family: 'Open Sans', HelveticaNeue-Light, 'Helvetica Neue Light', 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 12.6000003814697px; line-height: 25.2000007629395px; text-align: justify; background-color: rgb(236, 236, 236);">Connect init 采用APR的情况</span>
[java] view plaincopy
  1. //1 HTTP/1.1协议连接器
  2. org.apache.catalina.connector.Connector#init
  3. ->org.apache.coyote.http11.Http11AprProtocol#init
  4. -->org.apache.tomcat.util.net.AprEndpoint#init
  5. (org.apache.coyote.http11.Http11AprProtocol.Http11ConnectionHandler)
  6. // 2 AJP/1.3协议连接器
  7. org.apache.catalina.connector.Connector#init
  8. ->org.apache.coyote.ajp.AjpAprProtocol#init
  9. -->org.apache.tomcat.util.net.AprEndpoint#init
  10. (org.apache.coyote.ajp.AjpAprProtocol.AjpConnectionHandler)
[java] view plaincopy
  1. <pre name="code" class="java" style="color: rgb(102, 102, 102); font-size: 14px; line-height: 28px; text-align: justify;"><span style="font-family: 'Open Sans', HelveticaNeue-Light, 'Helvetica Neue Light', 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 12.6000003814697px; line-height: 25.2000007629395px; background-color: rgb(236, 236, 236);">Connector init 不采用APR的情况</span>
[java] view plaincopy
  1. <span style="font-family: 'Open Sans', HelveticaNeue-Light, 'Helvetica Neue Light', 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 12.6000003814697px; line-height: 25.2000007629395px; background-color: rgb(236, 236, 236);"></span><pre style="margin-top: 0px; margin-bottom: 0px; padding: 0px; border: none; font-family: Menlo, Monaco, 'Andale Mono', 'lucida console', 'Courier New', monospace; line-height: 1.5; overflow-x: auto; font-size: 14px; background: none rgb(255, 255, 255);"><pre name="code" class="java">// 1 HTTP/1.1协议连接器
  2. org.apache.catalina.connector.Connector#init
  3. ->org.apache.coyote.http11.Http11Protocol#init
  4. -->org.apache.tomcat.util.net.JIoEndpoint#init
  5. (org.apache.coyote.http11.Http11Protocol.Http11ConnectionHandler)
  6. // 2 AJP/1.3协议连接器
  7. org.apache.catalina.connector.Connector#init
  8. ->org.apache.coyote.ajp.AjpProtocol#init
  9. -->org.apache.tomcat.util.net.JIoEndpoint#init
  10. (org.apache.coyote.ajp.AjpProtocol.AjpConnectionHandler)

这里需要注意,除了JIoEndpoint外,还有NIoEndpoint,对于Tomcat7.0.24的代码,并没有采用NIOEndPoint,NIOEndpoint采用了NIO的方式进行Socket的处理。

最后,咋们再来看看org.apache.tomcat.util.net.JIoEndpoint#init的初始化过程,我们首先来看一下JIoEndpoint的继承关系图如下:


通过上图我们知道JIoEndpoint继承了AbstractEndpoint,而通过查看源码可知,JIoEndpoint没有实现自己的init方法,它默认采用了父类的init方法,那么我们就来看看AbstractEndpoint的init,它的代码如下:

[java] view plaincopy
  1. public final void init() throws Exception {
  2. if (bindOnInit) {
  3. bind();
  4. bindState = BindState.BOUND_ON_INIT;
  5. }
  6. }

通过查看上面的代码可知,因为bindOnInit默认是true,所以init调用了bind方法,而bind方法是抽象方法,最终由JIoEndpoint来实现,代码如下:

[java] view plaincopy
  1. @Override
  2. public void bind() throws Exception {
  3. // Initialize thread count defaults for acceptor
  4. if (acceptorThreadCount == 0) {
  5. acceptorThreadCount = 1;
  6. }
  7. // Initialize maxConnections
  8. if (getMaxConnections() == 0) {
  9. // User hasn't set a value - use the default
  10. setMaxConnections(getMaxThreadsExecutor(true));
  11. }
  12. if (serverSocketFactory == null) {
  13. if (isSSLEnabled()) {
  14. serverSocketFactory =
  15. handler.getSslImplementation().getServerSocketFactory(this);
  16. } else {
  17. serverSocketFactory = new DefaultServerSocketFactory(this);
  18. }
  19. }
  20. if (serverSocket == null) {
  21. try {
  22. if (getAddress() == null) {
  23. serverSocket = serverSocketFactory.createSocket(getPort(),
  24. getBacklog());
  25. } else {
  26. serverSocket = serverSocketFactory.createSocket(getPort(),
  27. getBacklog(), getAddress());
  28. }
  29. } catch (BindException orig) {
  30. String msg;
  31. if (getAddress() == null)
  32. msg = orig.getMessage() + " <null>:" + getPort();
  33. else
  34. msg = orig.getMessage() + " " +
  35. getAddress().toString() + ":" + getPort();
  36. BindException be = new BindException(msg);
  37. be.initCause(orig);
  38. throw be;
  39. }
  40. }
  41. }

通过上面代码可以看出,最终是调用了org.apache.tomcat.util.net.ServerSocketFactory#createSocket方法创建一个java.net.ServerSocket,并绑定在conf/server.xml中Connector中配置的端口。

综上我们可以得出如下结论:

Connector#init的时候,无论是AJP还是HTTP最终其实是调用了JioEndpoint的初始化,默认情况在初始化的时候就会创建java.net.ServerSocket绑到到配置的端口上。

Connector#start

接着我们再来分析一下Connector#start,因为Connector符合LifeCycle模板方法生命周期管理的机制,因此它的start最终会调用startInternal,org.apache.catalina.connector.Connector#startInternal代码如下:

[java] view plaincopy
  1. protected void startInternal() throws LifecycleException {
  2. // Validate settings before starting
  3. if (getPort() < 0) {
  4. throw new LifecycleException(sm.getString(
  5. "coyoteConnector.invalidPort", Integer.valueOf(getPort())));
  6. }
  7. setState(LifecycleState.STARTING);
  8. try {
  9. protocolHandler.start();
  10. } catch (Exception e) {
  11. String errPrefix = "";
  12. if(this.service != null) {
  13. errPrefix += "service.getName(): \"" + this.service.getName() + "\"; ";
  14. }
  15. throw new LifecycleException
  16. (errPrefix + " " + sm.getString
  17. ("coyoteConnector.protocolHandlerStartFailed"), e);
  18. }
  19. mapperListener.start();
  20. }

通过上面的代码,我们可以清晰的看到最终调用了protocolHandler.start(),而根据Connector#init流程的分析,这里会分是否采用APR,默认是不采用APR的,这里会根据不同的协议(AJP,HTTP)来调用对应的org.apache.coyote.ProtocolHandler#start. 其中AJP会采用org.apache.coyote.ajp.AjpProtocol,HTTP协议采用org.apache.coyote.http11.Http11Protocol,而无论是AjpProtocol还是Http11Protocol都会调用JIoEndpoint的方法,那么接下来我们就来看看JioEndpoint的start方法,它的代码如下:

[java] view plaincopy
  1. <span style="color: rgb(102, 102, 102); font-family: 'Open Sans', HelveticaNeue-Light, 'Helvetica Neue Light', 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 12.6000003814697px; line-height: 25.2000007629395px; text-align: justify; background-color: rgb(236, 236, 236);">org.apache.tomcat.util.net.JIoEndpoint#startInternal</span>
[java] view plaincopy
  1. public void startInternal() throws Exception {
  2. if (!running) {
  3. running = true;
  4. paused = false;
  5. // Create worker collection
  6. if (getExecutor() == null) {
  7. createExecutor();
  8. }
  9. initializeConnectionLatch();
  10. startAcceptorThreads();
  11. // Start async timeout thread
  12. Thread timeoutThread = new Thread(new AsyncTimeout(),
  13. getName() + "-AsyncTimeout");
  14. timeoutThread.setPriority(threadPriority);
  15. timeoutThread.setDaemon(true);
  16. timeoutThread.start();
  17. }
  18. }

从上面的代码可以看出,启动了Acceptor线程和AsyncTimeout线程,首先来看看Acceptor线程,我们再来看看startAcceptorThreads方法,代码如下:

[java] view plaincopy
  1. <span style="color: rgb(102, 102, 102); font-family: 'Open Sans', HelveticaNeue-Light, 'Helvetica Neue Light', 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 12.6000003814697px; line-height: 25.2000007629395px; text-align: justify; background-color: rgb(236, 236, 236);">org.apache.tomcat.util.net.AbstractEndpoint#startAcceptorThreads</span>
[java] view plaincopy
  1. protected final void startAcceptorThreads() {
  2. int count = getAcceptorThreadCount();
  3. acceptors = new Acceptor[count];
  4. for (int i = 0; i < count; i++) {
  5. acceptors[i] = createAcceptor();
  6. String threadName = getName() + "-Acceptor-" + i;
  7. acceptors[i].setThreadName(threadName);
  8. Thread t = new Thread(acceptors[i], threadName);
  9. t.setPriority(getAcceptorThreadPriority());
  10. t.setDaemon(getDaemon());
  11. t.start();
  12. }
  13. }

通过上面的代码,我们可以看出其实是通过org.apache.tomcat.util.net.AbstractEndpoint.Acceptor这个Runable接口的实现类来启动线程,接下来我们就来看看Acceptor#run方法,通过查看run方法,它里面其实就是调用了java.net.ServerSocket#accept的方法来接受一个Socket连接。

启动完了Acceptor线程以后,接着就会启动AsyncTimeout线程,而这里面需要注意的时候,无论是Acceptor还是AsyncTimeout线程,它们都是Daemon线程,而设置为Daemon的原因,我们会在下篇Tomcat的关闭中进行说明。

StandardEngine#start

从本文上面的分析中,我们得知StandardEngine继承了ContainerBase,而StandardEngine的startInternal钩子方法也仅仅是调用了父类ContainerBase的startInternal方法,那接下来我们分析一下ContainerBase的startInternal方法,代码如下:

[java] view plaincopy
  1. <span style="color: rgb(102, 102, 102); font-family: 'Open Sans', HelveticaNeue-Light, 'Helvetica Neue Light', 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 12.6000003814697px; line-height: 25.2000007629395px; text-align: justify; background-color: rgb(236, 236, 236);">org.apache.catalina.core.ContainerBase#startInternal</span>
[java] view plaincopy
  1. protected synchronized void startInternal() throws LifecycleException {
  2. // Start our child containers, if any
  3. Container children[] = findChildren();
  4. List<Future<Void>> results = new ArrayList<Future<Void>>();
  5. for (int i = 0; i < children.length; i++) {
  6. results.add(startStopExecutor.submit(new StartChild(children[i])));
  7. }
  8. boolean fail = false;
  9. for (Future<Void> result : results) {
  10. try {
  11. result.get();
  12. } catch (Exception e) {
  13. log.error(sm.getString("containerBase.threadedStartFailed"), e);
  14. fail = true;
  15. }
  16. }
  17. if (fail) {
  18. throw new LifecycleException(
  19. sm.getString("containerBase.threadedStartFailed"));
  20. }
  21. setState(LifecycleState.STARTING);
  22. }

我们删除了对本文的分析不相关的代码,只留下一些核心的代码,我们可以看到通过startStopExecutor异步的对子容器进行了启动,然后设置状态为STARTING的状态。而startStopExecutor是在容器的initInternal方法中进行初始化好的,接下来我们就来看看StartChild,StardChild的代码如下:

[java] view plaincopy
  1. <span style="color: rgb(102, 102, 102); font-family: 'Open Sans', HelveticaNeue-Light, 'Helvetica Neue Light', 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 12.6000003814697px; line-height: 25.2000007629395px; text-align: justify; background-color: rgb(236, 236, 236);">org.apache.catalina.core.ContainerBase.StartChild</span>
[java] view plaincopy
  1. private static class StartChild implements Callable<Void> {
  2. private Container child;
  3. public StartChild(Container child) {
  4. this.child = child;
  5. }
  6. @Override
  7. public Void call() throws LifecycleException {
  8. child.start();
  9. return null;
  10. }
  11. }

通过上面的代码,我们可以看到StartChild实现了Callable接口,实现这个接口的类可以将其放到对应的executor中执行(对于executor不熟悉的童鞋可以去看一下相关的文章,本文不做介绍),StartChild在运行的时候就会调用到子容器的start方法,而此时的父容器是StandardEngine,子容器就是StandardHost,接下来我们就来看看StandardHost的启动过程。通过前面对于init流程的分析,我们知道StandardHost不是在StandardEngine#init的时候初始化,因此在执行StandardHost#start的时候,要首先进行init方法的调用,具体的代码如下:

[java] view plaincopy
  1. <span style="color: rgb(102, 102, 102); font-family: 'Open Sans', HelveticaNeue-Light, 'Helvetica Neue Light', 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 12.6000003814697px; line-height: 25.2000007629395px; text-align: justify; background-color: rgb(236, 236, 236);">org.apache.catalina.util.LifecycleBase#start</span>
[java] view plaincopy
  1. public final synchronized void start() throws LifecycleException {
  2. if (state.equals(LifecycleState.NEW)) {
  3. init(); //因为此时的StandardHost还没有初始化,因此会走到这一步代码
  4. } else if (state.equals(LifecycleState.FAILED)){
  5. stop();
  6. } else if (!state.equals(LifecycleState.INITIALIZED) &&
  7. !state.equals(LifecycleState.STOPPED)) {
  8. invalidTransition(Lifecycle.BEFORE_START_EVENT);
  9. }
  10. setStateInternal(LifecycleState.STARTING_PREP, null, false);
  11. try {
  12. startInternal();
  13. } catch (Throwable t) {
  14. ExceptionUtils.handleThrowable(t);
  15. setStateInternal(LifecycleState.FAILED, null, false);
  16. throw new LifecycleException(
  17. sm.getString("lifecycleBase.startFail",toString()), t);
  18. }
  19. setStateInternal(LifecycleState.STARTED, null, false);
  20. }

上面省略了部分不相关的代码,在调用中我们可以清楚的看到,对于StandardHost的初始化,是在start的时候进行的。那接下来我们在来看一下StandardHost的init方法,通过查看代码,我们发现StandardHost本身没有实现initInternal的钩子方法,也就意味着最终初始化会调用ContainerBase#initInternal方法,而通过上文的描述,我们已经清楚ContainerBase#initInternal主要是初始化了一个startStopExecutor,这个线程池主要是为了异步的初始化子容器来用的。

我们知道StandardEngine初始化的时候,也是初始化了一个线程池,而StandardHost也初始化了一个线程池,他们的不同点在与创建线程的工厂方法不同,在采用缺省配置的情况下,StandardEngine的线程池中的线程是以Catalina-startStop的形式命名的,而StandardHost是以localhost-startStop的方式进行命名的。大家注意区分。

StandardHost#start调用init方法初始化完StandardHost以后,会调用钩子的startInternal方法,而startInternal方法又是调用了ContainerBased#startInternal方法,而ContainerBase#startInternal方法最终又会去启动子容器的,对于StandardHost来说,子容器就是StandardContext。 因此分析到这里我们可以得出如下结论:

对于StandardEngine,StandardHost的启动,父容器在init的时候创建一个启动和停止子容器的线程池,然后父容器启动的时候首先通过异步的方式将子容器的启动通过org.apache.catalina.core.ContainerBase.StartChild提交到父容器中对应的线程池中进行启动,而子容器启动的时候首先会初始化,然后再启动。

另外这里还需要注意一点就是,StandEngine#start的时候,最终调用了ContainerBase#startInternal方法,而ContainerBase#startInternal的最后,调用了threadStart(),我们来看看它的代码如下:

[java] view plaincopy
  1. <span style="color: rgb(102, 102, 102); font-family: 'Open Sans', HelveticaNeue-Light, 'Helvetica Neue Light', 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 12.6000003814697px; line-height: 25.2000007629395px; text-align: justify; background-color: rgb(236, 236, 236);">org.apache.catalina.core.ContainerBase#threadStart</span>
[java] view plaincopy
  1. protected void threadStart() {
  2. if (thread != null)
  3. return;
  4. if (backgroundProcessorDelay <= 0)
  5. return;
  6. threadDone = false;
  7. String threadName = "ContainerBackgroundProcessor[" + toString() + "]";
  8. thread = new Thread(new ContainerBackgroundProcessor(), threadName);
  9. thread.setDaemon(true);
  10. thread.start();
  11. }

上面的代码,首先会判断backgroundProcessorDelay是否小于0,而这个值默认情况下是-1,也就意味这后面的代码不会运行,而对于StandardEngine来说,它将backgroundProcessorDelay的值在构造函数中赋值为了10,这样的话,当StandardEngine启动的时候,就会启动名称为“ContainerBackgroundProcessor[StandardEngine[Catalina]]”的线程。

经过上面的分析,我们已经清楚了StandardEngine启动的过程了,但是我们还有一个地方需要进一步的分析。因为上面的分析我们仅仅只是分析了容器通过conf/server.xml配置文件的配置结构进行的启动,而我们都知道CATALINA-HOME/webapps/中的应用也是需要启动的,那么webapps目录的应用又是如何启动的呢?我们下面来分析一下,通过Tomcat总体结构的描述,我们已经知道,webapps目录下面的应用其实是属于Context的,而Context对应Tomcat中的StandardContext类,因此我们就知道应该对谁下手了,知道了目标以后,咋们还是采用之前的那种方式,要么debug,要么打印调用栈,这里我们还是通过打印调用栈的方式进行,我们在org.apache.catalina.core.StandardContext#initInternal中增加打印调用栈的方法,具体代码如下:

[java] view plaincopy
  1. protected void initInternal() throws LifecycleException {
  2. super.initInternal();
  3. Throwable ex = new Throwable();
  4. StackTraceElement[] stackElements = ex.getStackTrace();
  5. if (stackElements != null) {
  6. for (int i = stackElements.length - 1; i >= 0; i--) {
  7. System.out.print(stackElements[i].getClassName() + "\t");
  8. System.out.print(stackElements[i].getMethodName() + "\t");
  9. System.out.print(stackElements[i].getFileName() + "\t");
  10. System.out.println(stackElements[i].getLineNumber());
  11. }
  12. }
  13. if (processTlds) {
  14. this.addLifecycleListener(new TldConfig());
  15. }
  16. // Register the naming resources
  17. if (namingResources != null) {
  18. namingResources.init();
  19. }
  20. // Send j2ee.object.created notification
  21. if (this.getObjectName() != null) {
  22. Notification notification = new Notification("j2ee.object.created",
  23. this.getObjectName(), sequenceNumber.getAndIncrement());
  24. broadcaster.sendNotification(notification);
  25. }
  26. }

运行代码,可以看到控制台有如下的输出:

[java] view plaincopy
  1. java.util.concurrent.ThreadPoolExecutor$Worker    run  ThreadPoolExecutor.java   918
  2. java.util.concurrent.ThreadPoolExecutor$Worker runTask  ThreadPoolExecutor.java   895
  3. java.util.concurrent.FutureTask   run  FutureTask.java   138
  4. java.util.concurrent.FutureTask$Sync   innerRun FutureTask.java   303
  5. java.util.concurrent.Executors$RunnableAdapter call Executors.java    439
  6. org.apache.catalina.startup.HostConfig$DeployDirectory  run  HostConfig.java   1671
  7. org.apache.catalina.startup.HostConfig deployDirectory  HostConfig.java   1113
  8. org.apache.catalina.core.StandardHost  addChild StandardHost.java 622
  9. org.apache.catalina.core.ContainerBase addChild ContainerBase.java    877
  10. org.apache.catalina.core.ContainerBase addChildInternal ContainerBase.java    901
  11. org.apache.catalina.util.LifecycleBase start    LifecycleBase.java    139
  12. org.apache.catalina.util.LifecycleBase init LifecycleBase.java    102
  13. org.apache.catalina.core.StandardContext   initInternal StandardContext.java  6449

通过查看控制台的输出,我们可以看到有一个org.apache.catalina.startup.HostConfig$DeployDirectory类,于是乎找到这个类去看看呗。打开一看它是一个Runable接口的实现类,因此我们推断它也是放到某个线程池中进行异步运行的,最终通过IntellIJ IDEA提供的类调用栈分析工具(ctrl+alt+h)得到DeployDirectory构造器方法的调用栈如下图所示:

通过上图我们可以清楚的看到,最终的调用方是org.apache.catalina.startup.HostConfig#lifecycleEvent,到这里我们就知道了Context的启动是通过某个组件的生命周期事件的监听器来启动的,而HostConfig到底是谁的监听器呢?通过名称我们应该可以猜测出它是StandardHost的监听器,那么它到底监听哪个事件呢?我们查看下org.apache.catalina.startup.HostConfig#lifecycleEvent的代码如下:
[java] view plaincopy
  1. public void lifecycleEvent(LifecycleEvent event) {
  2. // Identify the host we are associated with
  3. try {
  4. host = (Host) event.getLifecycle();
  5. if (host instanceof StandardHost) {
  6. setCopyXML(((StandardHost) host).isCopyXML());
  7. setDeployXML(((StandardHost) host).isDeployXML());
  8. setUnpackWARs(((StandardHost) host).isUnpackWARs());
  9. }
  10. } catch (ClassCastException e) {
  11. log.error(sm.getString("hostConfig.cce", event.getLifecycle()), e);
  12. return;
  13. }
  14. // Process the event that has occurred
  15. if (event.getType().equals(Lifecycle.PERIODIC_EVENT)) {
  16. check();
  17. } else if (event.getType().equals(Lifecycle.START_EVENT)) {
  18. start();
  19. } else if (event.getType().equals(Lifecycle.STOP_EVENT)) {
  20. stop();
  21. }
  22. }

通过上面的代码,我们可以看出监听的事件是Lifecycle.START_EVENT,而通过查看org.apache.catalina.LifecycleState的代码STARTING(true,Lifecycle.START_EVENT)就可以得知,此时生命周期状态应该是STARTING,到这里我们应该已经猜到了,HostConfig是在StandardHost#start的时候通过监听器调用,为了验证我们的猜测,我们debug一下代码,我们可以在HostConfig#start方法中打个断点,运行以后得到如下内存结构:


通过上图也就验证了我们刚才的猜测。

通过上面的分析我们清楚了webapps目录中context的启动,总结如下:

webapps目录中应用的启动在StandardHost#start的时候,通过Lifecycle.START_EVENT这个事件的监听器HostConfig进行进一步的启动。

综合上面的文章所述,最后我们再来一下总结,我们知道Java程序启动以后,最终会以进程的形式存在,而Java进程中又会有很多条线程存在,因此最后我们就来看看Tomcat启动以后,到底启动了哪些线程,通过这些我们可以反过来验证我们对源代码的理解是否正确。接下来我们启动Tomcat,然后运行jstack -l <pid>来看看,在笔者的机器上面,jstack的输入如下所示:

[java] view plaincopy
  1. Full thread dump Java HotSpot(TM) 64-Bit Server VM (20.51-b01-457 mixed mode):
  2. "ajp-bio-8009-AsyncTimeout" daemon prio=5 tid=7f8738afe000 nid=0x115ad6000 waiting on condition [115ad5000]
  3. java.lang.Thread.State: TIMED_WAITING (sleeping)
  4. at java.lang.Thread.sleep(Native Method)
  5. at org.apache.tomcat.util.net.JIoEndpoint$AsyncTimeout.run(JIoEndpoint.java:148)
  6. at java.lang.Thread.run(Thread.java:680)
  7. Locked ownable synchronizers:
  8. - None
  9. "ajp-bio-8009-Acceptor-0" daemon prio=5 tid=7f8738b05800 nid=0x1159d3000 runnable [1159d2000]
  10. java.lang.Thread.State: RUNNABLE
  11. at java.net.PlainSocketImpl.socketAccept(Native Method)
  12. at java.net.PlainSocketImpl.accept(PlainSocketImpl.java:439)
  13. - locked <7f46a8710> (a java.net.SocksSocketImpl)
  14. at java.net.ServerSocket.implAccept(ServerSocket.java:468)
  15. at java.net.ServerSocket.accept(ServerSocket.java:436)
  16. at org.apache.tomcat.util.net.DefaultServerSocketFactory.acceptSocket(DefaultServerSocketFactory.java:60)
  17. at org.apache.tomcat.util.net.JIoEndpoint$Acceptor.run(JIoEndpoint.java:216)
  18. at java.lang.Thread.run(Thread.java:680)
  19. Locked ownable synchronizers:
  20. - None
  21. "http-bio-8080-AsyncTimeout" daemon prio=5 tid=7f8735acb800 nid=0x1158d0000 waiting on condition [1158cf000]
  22. java.lang.Thread.State: TIMED_WAITING (sleeping)
  23. at java.lang.Thread.sleep(Native Method)
  24. at org.apache.tomcat.util.net.JIoEndpoint$AsyncTimeout.run(JIoEndpoint.java:148)
  25. at java.lang.Thread.run(Thread.java:680)
  26. Locked ownable synchronizers:
  27. - None
  28. "http-bio-8080-Acceptor-0" daemon prio=5 tid=7f8735acd000 nid=0x1157cd000 runnable [1157cc000]
  29. java.lang.Thread.State: RUNNABLE
  30. at java.net.PlainSocketImpl.socketAccept(Native Method)
  31. at java.net.PlainSocketImpl.accept(PlainSocketImpl.java:439)
  32. - locked <7f46a8690> (a java.net.SocksSocketImpl)
  33. at java.net.ServerSocket.implAccept(ServerSocket.java:468)
  34. at java.net.ServerSocket.accept(ServerSocket.java:436)
  35. at org.apache.tomcat.util.net.DefaultServerSocketFactory.acceptSocket(DefaultServerSocketFactory.java:60)
  36. at org.apache.tomcat.util.net.JIoEndpoint$Acceptor.run(JIoEndpoint.java:216)
  37. at java.lang.Thread.run(Thread.java:680)
  38. Locked ownable synchronizers:
  39. - None
  40. "ContainerBackgroundProcessor[StandardEngine[Catalina]]" daemon prio=5 tid=7f8732850800 nid=0x111203000 waiting on condition [111202000]
  41. java.lang.Thread.State: TIMED_WAITING (sleeping)
  42. at java.lang.Thread.sleep(Native Method)
  43. at org.apache.catalina.core.ContainerBase$ContainerBackgroundProcessor.run(ContainerBase.java:1508)
  44. at java.lang.Thread.run(Thread.java:680)
  45. Locked ownable synchronizers:
  46. - None
  47. "main" prio=5 tid=7f8735000800 nid=0x10843e000 runnable [10843c000]
  48. java.lang.Thread.State: RUNNABLE
  49. at java.net.PlainSocketImpl.socketAccept(Native Method)
  50. at java.net.PlainSocketImpl.accept(PlainSocketImpl.java:439)
  51. - locked <7f32ea7c8> (a java.net.SocksSocketImpl)
  52. at java.net.ServerSocket.implAccept(ServerSocket.java:468)
  53. at java.net.ServerSocket.accept(ServerSocket.java:436)
  54. at org.apache.catalina.core.StandardServer.await(StandardServer.java:452)
  55. at org.apache.catalina.startup.Catalina.await(Catalina.java:779)
  56. at org.apache.catalina.startup.Catalina.start(Catalina.java:725)
  57. at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
  58. at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
  59. at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
  60. at java.lang.reflect.Method.invoke(Method.java:597)
  61. at org.apache.catalina.startup.Bootstrap.start(Bootstrap.java:322)
  62. at org.apache.catalina.startup.Bootstrap.main(Bootstrap.java:456)
  63. at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
  64. at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
  65. at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
  66. at java.lang.reflect.Method.invoke(Method.java:597)
  67. at com.intellij.rt.execution.application.AppMain.main(AppMain.java:120)

上面的截图我已经取消了JVM自己本生的线程,从上图中我们可以清楚的看到,有6条线程,其中ajp-bio-8009-AsyncTimeoutajp-bio-8009-Acceptor-0是在Ajp的Connector启动的时候启动的,http-bio-8080-AsyncTimeouthttp-bio-8080-Acceptor-0是http的Connector启动的时候启动的,ContainerBackgroundProcessor[StandardEngine[Catalina]]是在StandardEngine启动的时候启动的,而main线程就是我们的主线程。这里还需要注意一点就是除了Main线程以外,其它的线程都是Dameon线程,相关的内容在下篇Tomcat的关闭我们再来详细说明。

Tomcat源码解析三:tomcat的启动过程相关推荐

  1. Fabric1.4源码解析:Peer节点启动过程

    看一下Peer节点的启动过程,通常在Fabric网络中,Peer节点的启动方式有两种,通过Docker容器启动,或者是通过执行命令直接启动. 一般情况下,我们都是执行docker-compose -f ...

  2. Tomcat源码解析五:Tomcat请求处理过程

    前面已经分析完了Tomcat的启动和关闭过程,本篇就来接着分析一下Tomcat中请求的处理过程. 在开始本文之前,咋们首先来看看一个Http请求处理的过程,一般情况下是浏览器发送http请求-> ...

  3. Tomcat源码解析系列二:Tomcat总体架构

    Tomcat即是一个HTTP服务器,也是一个servlet容器,主要目的就是包装servlet,并对请求响应相应的servlet,纯servlet的web应用似乎很好理解Tomcat是如何装载serv ...

  4. 源码解析 使用tomcat作为web容器时,用到的外观模式

    源码解析 使用tomcat作为web容器时,接收浏览器发送过来的请求, tomcat会将请求信息封装成ServletRequest对象, 如下图①处对象. 但是大家想想ServletRequest是一 ...

  5. Myth源码解析系列之五- 服务启动源码解析

    通过前面几篇文章,我们搭建了环境,也进行了分布式事务服务的体验,相信大家对myth也有了一个大体直观的了解,接下来我们将正式步入源码解析之旅~~ order服务启动源码解析(myth-demo-spr ...

  6. 4、Eureka 源码解析 之 Eureka Client 启动原理分析

    在前面的一篇文章 3.Eureka 源码解析 之 Eureka Server 启动原理分析当中我们分析了一下 Eureka Server 的启动.在集群环境下 Eureka Server 相互之前需要 ...

  7. Netty 源码解析系列-服务端启动流程解析

    netty源码解析系列 Netty 源码解析系列-服务端启动流程解析 Netty 源码解析系列-客户端连接接入及读I/O解析 五分钟就能看懂pipeline模型 -Netty 源码解析 1.服务端启动 ...

  8. Disruptor源码解析三 RingBuffer解析

    目录 系列索引 前言 主要内容 RingBuffer的要点 源码解析 系列索引 Disruptor源码解析一 Disruptor高性能之道 Disruptor源码解析二 Sequence相关类解析 D ...

  9. Tomcat 源码解析一初识

      为什么我想研究Tomcat源码,我们现在都用的是SpringBoot开发项目,而SpringBoot对错Tomcat集成,导致现在基本上看不到Tomcat的身影了,但是Tomcat不存在吗?只要我 ...

最新文章

  1. 原来你离进大厂,只差这些面试题!
  2. 疯狂捕鱼之路径解决方案的思考
  3. fcntl函数完成 set_fl()函数还有clr_fl()函数的封装
  4. 三分钟玩转jQuery.noConflict()
  5. mysql安装包下载密码_MySQL解压包的安装与下载的图文教程
  6. T-SQL查询-逻辑查询处理
  7. 如何理解np.sum tf.reduce_sum( tf.reduce_max tf.reduce_mean)等对tensor和高维矩阵的axis选择的操作
  8. 牛客网编程题04--字符串处理
  9. 人工智能TensorFlow工作笔记007---认识张量
  10. php如何用菜刀连接getshell,xise菜刀后门箱子制作:Sqlmao连接Mysql实现Getshell
  11. keepalived漂移VIP故障
  12. Configure universal group membership caching
  13. java记事本教程_使用记事本开发java程序的步骤
  14. kepware mysql_Kepware EX6与MySQL数据库通讯(上篇)
  15. Ubuntu20.04 截图工具推荐
  16. Postman——pre-request产生随机字符串和随机数字传给request body
  17. 1229: 求三个数的平均数
  18. PHP实现分页功能(一)上一页/下一页(含有分类功能的分页)
  19. 计算机怎么升级64位操作系统,32位的电脑系统怎么升级成64位?
  20. 使用mediapipe和OpenCV实现摄像头实时人脸检测

热门文章

  1. 对爬虫爬取到的数据进行存储
  2. Linux文件系统与日志分析(inode、inode节点耗尽故障处理、文件备份和恢复、日志文件管理)
  3. 一个给自己挖坑的网络综合实验(后悔莫及)
  4. python宣传图片_宣传图片制作网站
  5. iis php mysql wiki_Windows下安装MediaWiki (iis+php+mysql+mediawiki)
  6. 用python批量创建docker_「docker实战篇」python的docker-docker镜像的创建使用dockerfile(3...
  7. 安卓机高的地图要用浏览器打开_浏览器F12操作概述
  8. android倒角按钮,Android实现圆角Button按钮
  9. php面试带项目_PHP面试常用算法(推荐)
  10. php在html中生成option,php-如何为使用laravel表单生成器创建的select标签的选项指定HTML属性?...