Tomcat 启动初始化和停止

Tomcat 通过 server.xml 配置文件装配一系列组件,并且为组件设计生命周期接口,在容器启停时,协调控制组件的启动、初始化和停止。容器通常使用脚本启动,脚本主要是检查 Java 环境、设置 JVM 参数,调用 Bootstrap.start 启动。

Bootstrap 是 Catalina 的引导加载类,它构造了一个 commonLoader 类加载器,加载 ${catalina.base}/lib 目录下的类,目的是与应用程序级的类隔离,接下来详细分析每个过程。

在此之前可先了解一下,官网对启动过程的描述,以及提供的启动UML序列图,Startup.txt&UML,这个图囊括了启动过程中类的调用,但我第一次看这个图,也是一脸懵,以下描述的则是通过 DEBGUG 跟出来的,当回过头再看这个图,确实很有用。

初始化

初始化涉及到 server.xml 的解析,Tomcat 使用 Digester 解析,其工作原理理解了很简单,使用 sax 解析,在元素开始和结束,借助一个 ObjectStack 对象栈和一系列解析规则完成组件的初始化,它主要有三个基本规则:

  • ObjectCreateRule:根据指定的 ClassName 创建一个实例,元素开始时,压入对象栈,结束时,弹出对象栈
  • SetPropertiesRule:元素开始时,根据其属性,反射调用栈顶元素对应成员变量的 set 方法
  • SetNextRule:元素结束时,使用set方法建立栈顶元素(child)和(top-1)元素(parent)的父子(组合)关系

各组件都是使用这三个规则进行配置解析,解析过程不再赘述,(这里) 提供一份源码注释,可加断点跟一下,着重关注栈内对象的入栈和出栈,值得注意的是在 GlobalResourcesLifecycleListener 初始化时会触发加载解析 mbeans-descriptors.xml,这个过程太长,可使用断点跳过。

调用 StandardServer.initialize 开始组件的初始化,触发 INIT_EVENT 事件,详细过程:

  1. 初始化 StandardServer:首先触发各 Listener(具体有哪些,可查看xml的配置)的执行,然后初始化内部的 Services;
  2. Service 主要是初始化定义的 Connectors;
  3. 初始化 Connector:初始化 Adapter 和 ProtocolHandler,处理器有两种不同实现:
    • Http11NioProtocol 初始化:NioEndpoint 设置线程池名称、设置ConnectionHandler、设置接收和发送 ByteBuffer 容量;SSL相关实现初始化:
      • 初始化 NioEndpoint:初始化 ServerSocketChannel,设置成阻塞模式,绑定端口;设置 Acceptor、Poller 线程数目;初始化 SSL 信息;初始化 NioSelectorPool;
    • Http11Protocol 初始化:JIoEndpoint 设置线程池名和ConnectionHandler,初始化 ServerSocketFactory:
      • 初始化 JioEndpoint:设置 Acceptor 线程数;创建 ServerSocket 并绑定端口。
  4. 初始化完毕

以上就是在组件在init生命周期事件中完成的设置,注意在 Digester 解析过程中,也完成了一系列的设置。

启动

调用 StandardServer.start 开启组件的启动过程,触发 BEFORE_START_EVENT、START_EVENT、AFTER_START_EVENT 事件:

  1. 启动 StandardServer:触发执行各 Listener;启动内部的 Services;
  2. Service 默认没有 Listener,首先启动 Engines 、接着启动 Executors、最后启动 Connectors;
  3. 启动 Engine,它调用 super.start() 进行启动或触发以下的动作:
    • 尝试启动 Manager、Cluster、Realm(LockOutRealm);
    • 启动子容器 Hosts;
    • EngineConfig 监听器的执行 - START_EVENT,STOP_EVENT;
    • 启动后台线程,定期检查会话超时。
  4. 启动 Host:设置 ErrorReportValve,并添加到自己的 pipeline 中,触发 ADD_VALVE_EVENT 容器事件,调用 super.start():
    • 启动子容器 Contexts;
    • 启动 pipeline 中实现 Lifecycle 的 Valve。
    • 触发 HostConfig 监听器的执行:
      • PERIODIC_EVENT:检查所有Web应用程序的状态;
      • START_EVENT:创建 Context,启动并部署 webapps & conf/Catalina/localhost/*.xml;
      • STOP_EVENT:取消已部署的所有应用。
  5. 启动 Context,部署 Web App
    • 根据 context.xml(或默认的)使用 Digester 创建 StandardContext 对象,添加 ContextConfig 监听器,通过 host.addChild 启动 Context;
    • 初始化用于解析 web.xml 的 Digester,创建设置 WebappLoader 且不使用"标准委托模型";
    • 先解析默认的 conf/web.xml,然后处理 WEB-INF/web.xml,创建 StandardWrapper 封装 Servlet。
  6. 启动 Connector,无 Listener,它主要是启动 ProtocolHandler:
    • Http11NioProtocol - 启动 NioEndpoint - 启动 Poller 线程,启动 Acceptor 线程;
    • Http11Protocol - 启动 JioEndpoint - 启动 Acceptor 线程。
  7. 注册 ShutdownHook,阻塞 main 线程,启动完毕。

接下来就该处理请求了,这部分下一篇会介绍。

停止

当调用 Bootstrap.stop 或接收到 SHUTDOWN 指令或者捕捉到系统关闭信号(如ctrl+c,shutdown,logoff)时,开始调用 Catalina.stop 方法,关闭组件,触发 BEFORE_STOP_EVENT、STOP_EVENT 事件:停止Server、Service,暂停 Connector,停止内部组件,停止 Connector,关闭线程池,打断 awaitThread 即 main 线程,结束。

如果 stop 命令执行失败,尝试使用系统信号终止,首先使用 kill -15 ,进程收到后进行处理;如果还是失败那么,等待 5s 后,使用 kill -9 强制终止。

小结

当我看到 Web应用加载的时候,耐心有点不足,感觉捋顺了整个过程,其实还有很多地方经不起推敲,为了看得更透彻,那么这个"简单"的启动过程,又有哪些值得思考的呢?

内部启动的线程和线程池中的线程为什么设置为守护线程(Daemon)?

Java 中有两类线程:守护线程与用户线程,区别是守护线程不会阻止 JVM 的退出。从 Tomcat 的停止流程来看,就算用非守护线程也不会出现什么问题,没有搜到官方对此的描述,这里按照理解强行解释一波 :)。

守护线程一般是服务提供者,运行系统代码,比如 GC 线程,就像操作系统中也区分用户线程和内核线程那样,Tomcat 将内部线程设为 daemon,也能很好的在语义上区分 Servlet 启动的线程,并且对于 Servlet 来说 Tomcat 就是它的操作系统。

生命周期的设计有什么好处?

保证组件启动和停止的一致性,为生命周期事件添加监听器,这些监听器处理其感兴趣的事件,来做一些额外的操作。这是观察者模式的应用。

多应用间如何实现隔离?

应用隔离的本质就是类隔离,主要防止类冲突。类是否相等是由其全限定名和类加载器共同决定的,容器就是通过自定义 ClassLoader 实现应用间隔离,Tomcat 类加载器结构:

classloader

当要求类加载器加载类时,它首先将请求委托给父加载器,然后在父加载器找不到所请求的类时,查找自己的存储库。而 Webapp 加载器略有不同,它首先会在自己的资源库中搜索,而不是向上委托,打破了标准的委托机制,其类加载时按以下顺序查找资源库:

  • Bootstrap 和 System 已加载的类
  • /WEB-INF/classes 和 /WEB-INF/lib/*.jar
  • Common 已加载的类

应用热加载和热部署?

当在启动 Engine 时,会新建一个名为 ContainerBackgroundProcessor[StandardEngine[Catalina]] 的线程,默认 10s 检查是否要重新加载或重新部署,对应方法在 Loader 接口定义分别是 backgroundProcess 和 modified。

默认 web.xml 配置了什么?

  • 提供一个 DefaultServlet,用于处理静态资源和未找到匹配 Servlet 的请求;
  • 用于编译和执行 JSP 的 JspServlet;
  • Session 默认超时 30 minutes;
  • 默认 MIME Type 映射和 Welcome Files

其他能够想到的点

采用都是常用的数据结构,如 ArrayList、数组、HashMap等,Pipeline 采用链表实现,Digester 使用栈来解析。欢迎补充。

本文由 wskwbog 创作,采用 知识共享4.0 许可证 - 署名-非商业性使用-禁止演绎
本站文章除注明转载/出处外,均为本站原创或翻译

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,997评论 6 502
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,603评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,359评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,309评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,346评论 6 390
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,258评论 1 300
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,122评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,970评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,403评论 1 313
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,596评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,769评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,464评论 5 344
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,075评论 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,705评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,848评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,831评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,678评论 2 354

推荐阅读更多精彩内容