深入拆解Tomcat & Jetty
李号双
eBay技术主管
立即订阅
6067 人已学习
课程目录
已完结 44 讲
0/4登录后,你可以任选4讲全文学习。
开篇词 (1讲)
开篇词 | Java程序员如何快速成长?
免费
模块一 必备基础 (4讲)
01 | Web容器学习路径
02 | HTTP协议必知必会
03 | 你应该知道的Servlet规范和Servlet容器
04 | 实战:纯手工打造和运行一个Servlet
模块二 整体架构 (9讲)
05 | Tomcat系统架构(上): 连接器是如何设计的?
06 | Tomcat系统架构(下):聊聊多层容器的设计
07 | Tomcat如何实现一键式启停?
08 | Tomcat的“高层们”都负责做什么?
09 | 比较:Jetty架构特点之Connector组件
10 | 比较:Jetty架构特点之Handler组件
11 | 总结:从Tomcat和Jetty中提炼组件化设计规范
12 | 实战:优化并提高Tomcat启动速度
13 | 热点问题答疑(1):如何学习源码?
模块三 连接器 (9讲)
14 | NioEndpoint组件:Tomcat如何实现非阻塞I/O?
15 | Nio2Endpoint组件:Tomcat如何实现异步I/O?
16 | AprEndpoint组件:Tomcat APR提高I/O性能的秘密
17 | Executor组件:Tomcat如何扩展Java线程池?
18 | 新特性:Tomcat如何支持WebSocket?
19 | 比较:Jetty的线程策略EatWhatYouKill
20 | 总结:Tomcat和Jetty中的对象池技术
21 | 总结:Tomcat和Jetty的高性能、高并发之道
22 | 热点问题答疑(2):内核如何阻塞与唤醒进程?
模块四 容器 (8讲)
23 | Host容器:Tomcat如何实现热部署和热加载?
24 | Context容器(上):Tomcat如何打破双亲委托机制?
25 | Context容器(中):Tomcat如何隔离Web应用?
26 | Context容器(下):Tomcat如何实现Servlet规范?
27 | 新特性:Tomcat如何支持异步Servlet?
28 | 新特性:Spring Boot如何使用内嵌式的Tomcat和Jetty?
29 | 比较:Jetty如何实现具有上下文信息的责任链?
30 | 热点问题答疑(3):Spring框架中的设计模式
模块五 通用组件 (4讲)
31 | Logger组件:Tomcat的日志框架及实战
32 | Manager组件:Tomcat的Session管理机制解析
33 | Cluster组件:Tomcat的集群通信原理
特别放送 | 如何持续保持对学习的兴趣?
模块六 性能优化 (8讲)
34 | JVM GC原理及调优的基本思路
35 | 如何监控Tomcat的性能?
36 | Tomcat I/O和线程池的并发调优
37 | Tomcat内存溢出的原因分析及调优
38 | Tomcat拒绝连接原因分析及网络优化
39 | Tomcat进程占用CPU过高怎么办?
40 | 谈谈Jetty性能调优的思路
41 | 热点问题答疑(4): Tomcat和Jetty有哪些不同?
结束语 (1讲)
结束语 | 静下心来,品味经典
深入拆解Tomcat & Jetty
登录|注册

08 | Tomcat的“高层们”都负责做什么?

李号双 2019-05-28
使用过 Tomcat 的同学都知道,我们可以通过 Tomcat 的/bin目录下的脚本startup.sh来启动 Tomcat,那你是否知道我们执行了这个脚本后发生了什么呢?你可以通过下面这张流程图来了解一下。
1.Tomcat 本质上是一个 Java 程序,因此startup.sh脚本会启动一个 JVM 来运行 Tomcat 的启动类 Bootstrap。
2.Bootstrap 的主要任务是初始化 Tomcat 的类加载器,并且创建 Catalina。关于 Tomcat 为什么需要自己的类加载器,我会在专栏后面详细介绍。
3.Catalina 是一个启动类,它通过解析server.xml、创建相应的组件,并调用 Server 的 start 方法。
4.Server 组件的职责就是管理 Service 组件,它会负责调用 Service 的 start 方法。
5.Service 组件的职责就是管理连接器和顶层容器 Engine,因此它会调用连接器和 Engine 的 start 方法。
这样 Tomcat 的启动就算完成了。下面我来详细介绍一下上面这个启动过程中提到的几个非常关键的启动类和组件。
你可以把 Bootstrap 看作是上帝,它初始化了类加载器,也就是创造万物的工具。
取消
完成
0/1000字
划线
笔记
复制
© 版权归极客邦科技所有,未经许可不得传播售卖。 页面已增加防盗追踪,如有侵权极客邦将依法追究其法律责任。
该试读文章来自付费专栏《深入拆解Tomcat & Jetty 》,如需阅读全部文章,
请订阅文章所属专栏。
立即订阅
登录 后留言

精选留言(32)

  • why
    - Tomcat 本质是 Java 程序, [startup.sh](http://startup.sh) 启动 JVM 运行 Tomcat 启动类 bootstrap
    - Bootstrap 初始化类加载器, 创建 Catalina
    - Catalina 解析 server.xml, 创建相应组件, 调用 Server start 方法
    - Server 组件管理 Service 组件并调用其 start 方法
    - Service 负责管理连接器和顶层容器 Engine, 其会调用 Engine start 方法
    - 这些类不处理具体请求, 主要管理下层组件, 并分配请求
    - Catalina 完成两个功能
        - 解析 server.xml, 创建定义的各组件, 调用 server init 和 start 方法
        - 处理异常情况, 例如 ctrl + c 关闭 Tomcat. 其会在 JVM 中注册"关闭钩子"
            - 关闭钩子, 在关闭 JVM 时做清理工作, 例如刷新缓存到磁盘
            - 关闭钩子是一个线程, JVM 停止前会执行器 run 方法, 该 run 方法调用 server stop 方法
    - Server 组件, 实现类 StandServer
        - 继承了 LifeCycleBase
        - 子组件是 Service, 需要管理其生命周期(调用其 LifeCycle 的方法), 用数组保存多个 Service 组件, 动态扩容数组来添加组件
        - 启动一个 socket Listen停止端口, Catalina 启动时, 调用 Server await 方法, 其创建 socket Listen 8005 端口, 并在死循环中等连接, 检查到 shutdown 命令, 调用 stop 方法
    - Service 组件, 实现类 StandService
        - 包含 Server, Connector, Engine 和 Mapper 组件的成员变量
        - 还包含 MapperListener 成员变量, 以支持热部署, 其Listen容器变化, 并更新 Mapper, 是观察者模式
        - 需注意各组件启动顺序, 根据其依赖关系确定
            - 先启动 Engine, 再启动 Mapper Listener, 最后启动连接器, 而停止顺序相反.
    - Engine 组件, 实现类 StandEngine 继承 ContainerBase
        - ContainerBase 实现了维护子组件的逻辑, 用 HaspMap 保存子组件, 因此各层容器可重用逻辑
        - ContainerBase 用专门线程池启动子容器, 并负责子组件启动/停止, "增删改查"
        - 请求到达 Engine 之前, Mapper 通过 URL 定位了容器, 并存入 Request 中. Engine 从 Request 取出 Host 子容器, 并调用其 pipeline 的第一个 valve
    2019-06-02
    16
  • 一路远行
    加锁通常的场景是存在多个线程并发操作不安全的数据结构。

    不安全的数据结构:
    Server本身包含多个Service,内部实现上用数组来存储services,数组的并发操作(包含缩容,扩容)是不安全的。所以,在并发操作(添加/修改/删除/遍历等)services数组时,需要进行加锁处理。

    可能存在并发操作的场景:
    Tomcat提供MBean的机制对管理的对象进行并发操作,如添加/删除某个service。
    2019-05-28
    14
  • 轩恒
    有个常见问题请教一下,在实际应用场景中,tomcat在shutdown的时候,无法杀死java进程,还得kill,这是为何呢?

    作者回复: Tomcat会调用Web应用的代码来处理请求,可能Web应用代码阻塞在某个地方。

    2019-05-29
    8
  • allean
    老师的专栏思路清晰,讲解的深入但又通俗易懂,感谢大佬带我飞😁
    2019-05-28
    8
  • 大卫
    老师好,tomcat一般生产环境线程数大小建议怎么设置呢

    作者回复: 理论上:

    线程数=((线程阻塞时间 + 线程忙绿时间) / 线程忙碌时间) * cpu核数

    如果线程始终不阻塞,一直忙碌,会一直占用一个CPU核,因此可以直接设置 线程数=CPU核数。

    但是现实中线程可能会被阻塞,比如等待IO。因此根据上面的公式确定线程数。

    那怎么确定线程的忙碌时间和阻塞时间?要经过压测,在代码中埋点统计,专栏后面的调优环节会涉及到。

    2019-06-05
    7
  • Geek_ebda96
    老师最近遇到一个问题,刚到新公司,看他们把一个tomact的connector的线程池设置成800,这个太夸张了吧,connector的线程池只是用来处理接收的http请求,线程池不会用来处理其他业务本身的事情,设置再大也只能提高请求的并发,并不能提高系统的响应,让这个线程池干其他的事情,而且线程数太高,线程上下文切换时间也高,反而会降低系统的响应速度吧?我理解是不是对的,老师?还有一个问题就是设置的connector线程数,是tomcat启动的时候就会初始化这么多固定的线程还是这只是一个上限,还有就是如果线程处于空闲状态,会不会进行上下文切换呢?

    作者回复: 这里你误解了Connector中的线程池,这个线程池就是用来处理业务的。另外你提到线程数设的太高,会有线程切换的开销,这是对的。线程数具体设多少,根据具体业务而定,如果你的业务是IO密集型的,比如大量线程等待在数据库读写上,线程数应该设的越高。如果是CPU密集型,完全没有阻塞,设成CPU核数就行。800这个数有点高,我猜你们的应用属于IO密集型。

    2019-06-05
    1
    5
  • Monday
    listener对应的中文(三个字)竟然是敏感词。。。
    2019-05-29
    1
    4
  • allean
    如果映射关系不变,而是某个具体的Servlet的方法处理逻辑变了,热部署也可以解决重启tomcat的尴尬吗

    作者回复: 那不叫热部署,叫热加载。
    热部署和热加载都不需要重启Tomcat。

    2019-05-28
    4
  • W.T
    老师,以上源码是基于tomcat的哪个版本?

    作者回复: 最新9.x版

    2019-05-28
    3
  • What for
    老师您好,问个问题:
    文中提到用动态数组节省内存,据我所知对象数组里放的是引用而不是对象本身,所以理论上建一个稍微大一点的数组(比如说常见的 16)似乎并不会占用太多空间,请问我的理解有没有问题?

    作者回复: 没问题

    2019-07-23
    2
  • 星辰
    老师,我看的慢。

    一个子容器只有一个父容器, 如 a的父容器是容器b;
    那此时,只有父容器会调用子容器的start()方法吧?
    如果用synchronized同步互斥的方法保护调用子容器的start()方法,会不会有些多余?

    作者回复: 如果在嵌入式启动,不能预测用户的行为,可能不小心起了两个线程来启动同一个Server 实例,防范于未然,再说启动阶段加个保险也没多大开销,一次性的。

    2019-06-11
    2
  • Monday
    根据老师的给出的Github上Tomcat源码调试Tomcat的启动过程,遇到以下这个问题。
    经debug发现,运行完Catalina.load()方法的第566行digester.parse(inputSource)初始化了Server对象。但是我单步进入第566行,各种操作都没有跟踪到具体是哪一行初始化了Server对象。莫非有Listener?

    作者回复: digester应该是通过反射的方式创建Server对象的。

    2019-05-29
    2
  • wwm
    老师,请教一个问题:
    在Bootstrap中,基于什么原因用反射的方式创建Catalina实例,之后继续基于反射方式调用load、init、start这些方法?为什么不是直接new Catalina实例后通过实例直接调用这些方法?

    作者回复: Tomcat有自己的类加载器体系,Catalina相关的类都是由
    专门的类加载器catalinaLoader来加载:

    Class<?> startupClass = catalinaLoader.loadClass("org.apache.catalina.startup.Catalina")

    Tomcat的类加载器体系会有专门的文章来详细解释。

    2019-05-28
    2
  • 代码搬运工
    老师,1.catalina创建组件,是把所有的对象都new出来了吧,只是各个组件之间没有相互注入吧。
    2.为什么catalina直接调用server的start方法?不是先init吗?
    3.容器之间是什么时候注入进去的?还有listener是什么时候注入到组件中去的?

    作者回复: 1,对的,直接new出来
    2,start方法里调了init方法
    3,父容器应该是在构造函数里new了子容器

    2019-06-04
    1
  • 发条橙子 。
    老师,对于你的问题,实际上我也不理解为何要加锁 。
    首先,按理说server对每一个service开一个线程去初始化 。 应该不会多个线程对一个service同时初始化吧。
    再者,这块同步如果是要防止重复初始化,那应该在start()方法中做,否则等释放锁后,下一个线程获得锁还是会执行start()方法。

    所以这块加锁具体的作用我也看不懂,难道是起到多线程同步阻塞的作用??

    作者回复: 不管外面怎么调,加了锁这个方法就是线程安全的了

    2019-05-29
    1
    1
  • 清风
    调试源码,会遇到在执行不到对应的断点的情况,看调用栈是被之前的断点拦住了,这个有什么好的断点调试方法吗,这样调试太费劲了

    作者回复: IDE一般有disable断点的功能

    2019-05-29
    1
  • 飞翔
    老师能讲一下startup.sh里边的code 都啥意思不?

    作者回复: 关键就是启动了一个JVM进程去跑Tomcat的Bootstrap类。

    2019-05-29
    1
  • calljson
    热部署和热加载原理帮忙讲解下,还有强制停止比如杀进程等,怎么通过钩子处理的?

    作者回复: 热部署和热加载有专门的一篇详细解释,简单来说就是Tomcat启动了后台线程监控项目文件的变化,一旦变了就重新加载整个应用或个别Java类。

    kill -9 强杀的话,JVM钩子也没办法执行到。

    2019-05-28
    1
  • 迈克尔嘿嘿
    老师,我看的tomcat源码版本为8.5.47其中StandardService中的startInternal方法。 多了一个executors数组的启动,但是你的文章中没有写,是因为你的版本是9以后的原因吗?如果不是,这个executors在这是干嘛用的呢?希望老师能帮忙解惑,谢谢老师。
     // Start our defined Container first
            if (engine != null) {
                synchronized (engine) {
                    engine.start();
                }
            }

            synchronized (executors) {
                for (Executor executor: executors) {
                    executor.start();
                }
            }

            mapperListener.start();
    2019-11-09
  • Vettel
    思考题是service组件吧?
    2019-10-29
收起评论
32
返回
顶部