学习
实践
活动
专区
工具
TVP
写文章
专栏首页码云大作战tomcat-超详细的启动流程(start)

tomcat-超详细的启动流程(start)

一、Bootstrap.start()

(1)tomcat启动时会先调用脚本,在脚本运行时会启动Bootstrap的main方法,mian方法中会调用load方法进行初始化操作,通过责任链模式将所有结点初始化后,会调用start方法-该方法是tomcat启动的核心方法,即启动tomcat的核心线程。

在上一篇 - tomcat-超详细的启动流程(init)中提过这里的daemon实际上为catalina,因此调用catalina的start方法。

main方法中的源码如下:

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")) { //都会走进该if分支
        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 var4) {
    Throwable t = var4;
    if (var4 instanceof InvocationTargetException && var4.getCause() != null) {
        t = var4.getCause();
    }

    handleThrowable(t);
    t.printStackTrace();
    System.exit(1);
}

二、Catalina.start()

本质上与init()方法没啥区别,委派给下一个standardServer结点来进行start方法。

public void start() {

    if (getServer() == null) {
        load();
    }

    if (getServer() == null) {
        log.fatal(sm.getString("catalina.noServer"));
        return;
    }

    long t1 = System.nanoTime();
    try {        //standardServer的start方法        getServer().start();
    } catch (LifecycleException e) {
        log.fatal(sm.getString("catalina.serverStartFail"), e);
        try {
            getServer().destroy();
        } catch (LifecycleException e1) {
            log.debug("destroy() failed for failed Server ", e1);
        }
        return;
    }    //...    //在Bootstrap的main方法中 设置了为true,因此为调用await方法,保证主线程一直处于运行。
    if (await) {
        await();
        stop();
    }
}

这里的start方法也一样,由lifecycleBase来负责统一管理生命周期,并且子类会重新这个start方法,具体源码如下:

@Override
public final synchronized void start() throws LifecycleException {
    //...  上面不重要
    try {        //设置状态        setStateInternal(LifecycleState.STARTING_PREP, null, false);
        //该方法由子类重写
        startInternal();
        if (state.equals(LifecycleState.FAILED)) {
            stop();
        } else if (!state.equals(LifecycleState.STARTING)) {
            invalidTransition(Lifecycle.AFTER_START_EVENT);
        } else {
            setStateInternal(LifecycleState.STARTED, null, false);
        }
    } catch (Throwable t) {
        handleSubClassException(t, "lifecycleBase.startFail", toString());
    }
}

找到standardServer对应的startInternal方法。

三、StandardServer.startInternal()

在StandardServer.startInternal()中会继续通过责任链模式向后传递,进行StandardService的start方法调用。

@Override
protected void startInternal() throws LifecycleException {
//触发监听fireLifecycleEvent(CONFIGURE_START_EVENT, null);
    setState(LifecycleState.STARTING);
    globalNamingResources.start();
    
    //责任链继续向后传递,进行standardService的start方法    synchronized (servicesLock) {
        for (int i = 0; i < services.length; i++) {
            services[i].start();
        }
}//...
}

四、StandardService.startInternal()

从下图源码中可以看出,StandardService.startInternal(),先进行engine的start调用,再进行connector的start调用。

@Override
protected void startInternal() throws LifecycleException {

    if(log.isInfoEnabled())
        log.info(sm.getString("standardService.start.name", this.name));
    setState(LifecycleState.STARTING);
    // engine的start
    if (engine != null) {
        synchronized (engine) {
            engine.start();
        }
    }

    //server.xml中无配置executors,所以不会执行这段代码    synchronized (executors) {
        for (Executor executor: executors) {
            executor.start();
        }
    }

    mapperListener.start();
    //connector的start
    synchronized (connectorsLock) {
        for (Connector connector: connectors) {
            // If it has already failed, don't try and start it
            if (connector.getState() != LifecycleState.FAILED) {
                connector.start();
            }
        }
    }
}

(1)StandardEngine.startInternal()

本质上调用了父类ContainerBase.startInternal()。核心源码如下,在代码中主要作用为将tomcat容器以线程池的方式来启动,这个startStopExecutor是在tomcat的init中被构造出来的。

@Override
protected synchronized void startInternal() throws LifecycleException {
    // 前面代码我跳过了,不重要
    Container children[] = findChildren();
    List<Future<Void>> results = new ArrayList<>();
    for (int i = 0; i < children.length; i++) {
        results.add(startStopExecutor.submit(new StartChild(children[i])));
    }
    //...
}

tomcat容器的关系为:Engine——>Host——>Context——>Wrapper——>Servlet。

进入StartChild线程中可以看到线程实现方式为Callable,并且在child中又使用了start()。即Engine——>Host。

private static class StartChild implements Callable<Void> {

    private Container child;
    public StartChild(Container child) {
        this.child = child;
    }

    @Override
    public Void call() throws LifecycleException {
        child.start();
        return null;
    }
}

(2)StandardHost.startInternal()

StandardHost.startInternal()中又递归调用了父类ContainerBase.startInternal方法。即下一个线程池需要启动的子类为StandardContext。

@Override
protected synchronized void startInternal() throws LifecycleException {    // 前面代码...    super.startInternal();
}

(3)StandardContext.startInternal()

StandardContext.startInternal()这里的触发监听,比较核心,在内部会解析web.xml。

protected synchronized void startInternal() throws LifecycleException {
    // 前面代码...    try {             // 前面代码...            // 触发监听 - 解析web.xml
            fireLifecycleEvent(Lifecycle.CONFIGURE_START_EVENT, null);
            // 这里child为Context的子类 Wrapper的start的调用
            for (Container child : findChildren()) {
                if (!child.getState().isAvailable()) {
                    child.start();
                }
}//后面代码...}

触发监听,核心代码:

@Override
public void lifecycleEvent(LifecycleEvent event) {
    // 前面代码...
    if (event.getType().equals(Lifecycle.CONFIGURE_START_EVENT)) {        //触发监听的type为这一行        configureStart();
    } else if (event.getType().equals(Lifecycle.BEFORE_START_EVENT)) {
        beforeStart();
    } else if (event.getType().equals(Lifecycle.AFTER_START_EVENT)) {
        if (originalDocBase != null) {
            context.setDocBase(originalDocBase);
        }
    } else if (event.getType().equals(Lifecycle.CONFIGURE_STOP_EVENT)) {
        configureStop();
    } else if (event.getType().equals(Lifecycle.AFTER_INIT_EVENT)) {
        init();
    } else if (event.getType().equals(Lifecycle.AFTER_DESTROY_EVENT)) {
        destroy();
    }

}
protected synchronized void configureStart() {
    //前面代码...
    webConfig();
    // 后面代码...
}
**
 * Scan the web.xml files that apply to the web application and merge them
 * using the rules defined in the spec. For the global web.xml files,
 * where there is duplicate configuration, the most specific level wins. ie
 * an application's web.xml takes precedence over the host level or global
 * web.xml file.
 *///看英文注释可以知道,这个方法是用来解析tomcat的web.xmlprotected void webConfig() {

在webConfig中也可以找到我们经常会配置的一些解析方法,比如servlet、filter等。

下图为filter的解析。

for (FilterDef filter : webxml.getFilters().values()) {
    if (filter.getAsyncSupported() == null) {
        filter.setAsyncSupported("false");
    }
    context.addFilterDef(filter);
}
for (FilterMap filterMap : webxml.getFilterMappings()) {
    context.addFilterMap(filterMap);
}

下图为servlet的解析。

for (ServletDef servlet : webxml.getServlets().values()) {
    Wrapper wrapper = context.createWrapper();
    if (servlet.getLoadOnStartup() != null) {
        wrapper.setLoadOnStartup(servlet.getLoadOnStartup().intValue());
    }
    if (servlet.getEnabled() != null) {
        wrapper.setEnabled(servlet.getEnabled().booleanValue());
    }
wrapper.setName(servlet.getServletName());    // ...}

下图为listener的解析。

for (String listener : webxml.getListeners()) {
    context.addApplicationListener(listener);
}

(4)StandardWrapper.startInternal()

protected synchronized void startInternal() throws LifecycleException {

    //本质上也一样 调用父类的startInternal() 线程池启动
    super.startInternal();
    
}

这一步为止,tomcat容器(Engine—>Host—>Context—>Wrapper)启动完毕。

五、mapperListern.startInternal()

回到StandardService,会进行mapperListern的启动。找到子类重写的startInernal(),源码如下:

@Override
public void startInternal() throws LifecycleException {

setState(LifecycleState.STARTING);
    // 获取engine    Engine engine = service.getContainer();
    if (engine == null) {
        return;
    }

findDefaultHost();
    // 添加engine中的listerner    addListeners(engine);
    Container[] conHosts = engine.findChildren();
    for (Container conHost : conHosts) {
        Host host = (Host) conHost;
        if (!LifecycleState.NEW.equals(host.getState())) {
            //遍历host-注册host
            registerHost(host);
        }
    }
}
private void registerHost(Host host) {

    String[] aliases = host.findAliases();
    mapper.addHost(host.getName(), aliases, host);
    for (Container container : host.findChildren()) {
if (container.getState().isAvailable()) {//注册host下的contextregisterContext((Context) container);
        }
    }
}
private void registerContext(Context context) {

    String contextPath = context.getPath();
    if ("/".equals(contextPath)) {
        contextPath = "";
    }
    Host host = (Host)context.getParent();
    WebResourceRoot resources = context.getResources();
    String[] welcomeFiles = context.findWelcomeFiles();
    List<WrapperMappingInfo> wrappers = new ArrayList<>();
    for (Container container : context.findChildren()) {        //遍历contxt-注册wrapper        prepareWrapperMappingInfo(context, (Wrapper) container, wrappers);
        if(log.isDebugEnabled()) {
            log.debug(sm.getString("mapperListener.registerWrapper",
                    container.getName(), contextPath, service));
        }
    }
    //最后将host名+host端口+context+wrapper即servlet 添加到mapper中。
    mapper.addContextVersion(host.getName(), host, contextPath,
            context.getWebappVersion(), context, welcomeFiles, resources,
            wrappers);
    if(log.isDebugEnabled()) {
        log.debug(sm.getString("mapperListener.registerContext",
                contextPath, service));
    }
}

在mapper中会注册engine、host、context、wrapper,组成host名+host端口+context路径+context应用名+servlet添加到mapper中,因此当接受到http请求时,如果tomcat有多个工程,多个host情况下,可以根据url拆分然后然后根据mapper来进行匹配。

六、Connector.startInternal()

责任链模式委托protocolHandler.start(),实际上未NioEndpoint.startInternal()

protected void startInternal() throws LifecycleException {
    if (getPortWithOffset() < 0) {
        throw new LifecycleException(sm.getString(
                "coyoteConnector.invalidPort", Integer.valueOf(getPortWithOffset())));
    }

    setState(LifecycleState.STARTING);
    try {        //protocolHandler的启动        protocolHandler.start();
    } catch (Exception e) {
        throw new LifecycleException(
                sm.getString("coyoteConnector.protocolHandlerStartFailed"), e);
    }
}

七、NioEndpoint.startInternal()

(1)在NioEndpoint.startInternal()中会创建工作线程池、clientPoller线程池、acceptor线程池。

public void startInternal() throws Exception {

    if (!running) {
        //...
        if ( getExecutor() == null ) {            //启动工作线程,            createExecutor();
        }

        initializeConnectionLatch();
        
        pollers = new Poller[getPollerThreadCount()];
        for (int i=0; i<pollers.length; i++) {
            pollers[i] = new Poller();
            Thread pollerThread       //启动clientPoller线程
       = new Thread(pollers[i], getName() + "-ClientPoller-"+i);
            pollerThread.setPriority(threadPriority);
            pollerThread.setDaemon(true);
            pollerThread.start();
        }
        //创建acceptor线程
        startAcceptorThreads();
    }
}

最小线程数量默认为10,最大线程数量默认为200,也可读取server.xml中配置的线程数量。

public void createExecutor() {
    internalExecutor = true;
    TaskQueue taskqueue = new TaskQueue();
    TaskThreadFactory tf = new TaskThreadFactory(getName() + "-exec-", daemon, getThreadPriority());
    executor = new ThreadPoolExecutor(getMinSpareThreads(), getMaxThreads(), 60, TimeUnit.SECONDS,taskqueue, tf);
    taskqueue.setParent( (ThreadPoolExecutor) executor);
}

(2)acceptor线程

在下面方法中会启动,acceptor线程,并创建acceptor对象。

protected void startAcceptorThreads() {
    int count = getAcceptorThreadCount();
    acceptors = new ArrayList<>(count);
    for (int i = 0; i < count; i++) {
        Acceptor<U> acceptor = new Acceptor<>(this);
        String threadName = getName() + "-Acceptor-" + i;
        acceptor.setThreadName(threadName);
        acceptors.add(acceptor);
        Thread t = new Thread(acceptor, threadName);
        t.setPriority(getAcceptorThreadPriority());
        t.setDaemon(getDaemon());
        t.start();
    }
}

启动线程后,查询acceptor线程的run方法,发现acceptor中会建立socket连接,可以接收http请求。

@Override
public void run() {
    while (endpoint.isRunning()) {

        try {
            U socket = null;
            try {
                //接收socket
                socket = endpoint.serverSocketAccept();
            } catch (Exception ioe) {
               //...
            }
           
        } catch (Throwable t) {
           //...//...//...
        }
    }
}

到这一步tomcat完全启动成功,并且开启了对应的线程,可以接受http请求,处理http请求。

八、总结

文章分享自微信公众号:
码云大作战

本文参与 腾讯云自媒体分享计划 ,欢迎热爱写作的你一起参与!

作者:Y
原始发表时间:2020-07-09
如有侵权,请联系 cloudcommunity@tencent.com 删除。
登录 后参与评论
0 条评论

相关文章

  • tomcat-超详细的启动流程(init)

    本质上tomcat的启动流程和总体架构都离不开server.xml。在Server.xml中我们可以看到一些我们比较熟悉的配置。

    虞大大
  • springboot启动流程详解_网页解析的详细过程

    此文章讲解SpringBoot中配置Bean的几种形式,以及在SpringBoot启动流程中的先后顺序。

    全栈程序员站长
  • 超详细分析Bootloader(Uboot)到内核的启动流程(万字长文!)

      Bootloader的启动过程可以分为单阶段、多阶段两种。通常多阶段的 Bootloader能提供更为复杂的功能以及更好的可移植性。从固态存储设备上启动的 ...

    嵌入式与Linux那些事
  • 一条更新sql的完整执行流程(超详细)

    查询流程,我们是不是再研究下更新流程、插入流程和删除流程? 一条查询sql的完整执行流程(从连接到引擎,穿插涉及到的知识,超详细) 在数据库里面,我...

    向着百万年薪努力的小赵
  • 从源码的角度详细分析SpringBoot启动流程

    SpringBoot项目的启动流程是很多面试官面试中高级Java程序员喜欢问的问题。这个问题的答案涉及到了SpringBoot工程中的源码,也许我们之前看过别的...

    用户3587585
  • 一条查询sql的完整执行流程(从连接到引擎,穿插涉及到的知识,超详细)

    当我们的工具或者程序连接到数据库之后,实际上发生了什么事情?它的内部是怎么工作的? 就像我们到餐厅去吃饭,点了菜以后,过一会儿菜端上来了,后厨里面有...

    向着百万年薪努力的小赵
  • 13位Python大牛历时一个月打造的Python系统学习流程图,超详细!

    对于刚开始接触Python的小伙伴来说,没有思路方法,不知道从何开始学习,把软件环境安装好后就不知所措了!接下来我给大家分享下多位大牛倾力打造的python系...

    公众号---人生代码
  • Jenkins+SVN+Maven自动化部署环境搭建

    往期精选 环境准备 操作系统:Windows10 Java环境:下载 jdk-1.8.0-131-X64.zip,配置Java环境变量(参考:http://ji...

    企鹅号小编
  • 碎片化 | 第四阶段-33-Struts2-Spring整合环境概述讲解-视频

    如清晰度低,可转PC网页观看高清版本: http://v.qq.com/x/page/u0566wcfa99.html Struts2+Spring整合 1...

    码神联盟
  • docker-09

    Docker can build images automatically by reading the instructions from a Dockerf...

    Tom2Code
  • Elasticsearch +logstash +filebeat+redis+saltstack部署ELK日志平台

    ELK是三个开源软件的缩写,分别表示:Elasticsearch , Logstash, Kibana , 它们都是开源软件。新增了一个FileBeat,它是一...

    以谁为师
  • java.lang.OutOfMemoryError: Java heap space错误及处理办法(收集整理、转)

    下面是从网上找到的关于堆空间溢出的错误解决的方法: java.lang.OutOfMemoryError: Java heap space ========...

    全栈程序员站长
  • 推荐系统[一]:超详细知识介绍,一份完整的入门指南,解答推荐系统相关算法流程、衡量指标和应用,以及如何使用jieba分词库进行相似推荐

    如果说互联网的目标就是连接一切,那么推荐系统的作用就是建立更加有效率的连接,推荐系统可以更有效率的连接用户与内容和服务,节约了大量的时间和成本。

    汀丶人工智能
  • 部署云服务器--(2) 配置服务器坏境 运行Java程序

    上文我们成功申请到了阿里云服务器,这次我们需要把程序运行在服务器上了,下面我们分步骤来讲:

    浩Coding
  • Web后端开发入门(2)

    下拉,找到如上图所示位置,Core 核心:zip版,tar.gz版(Linux系统),32位版,64位版 ,安装版。前几个版本都不需要安装,如果你需要安...

    全栈程序员站长
  • 超详细讲解Sqoop2应用与实践

    摘要:超详细讲解Sqoop2应用与实践,从hdfs上的数据导入到postgreSQL中,再从postgreSQL数据库导入到hdfs上。详细讲解创建link和创...

    王小雷
  • tomcat-整启动流程-源码解析

    上文了解了大致tomat的相关架构,那么本文是针对tomcat的启动流程进行了解,。tomcat是通过Bootstrap的main方法进行启动,然后通过...

    逍遥壮士
  • MyEclipse10破解安装及Java环境基本配置

    首先安装及配置JDK环境,主要分为如下步骤:安装JDK-配置环境变量-检查是否安装成功。

    ITester软件测试小栈
  • 分布式资源调度框架YARN

    如图所示,1.x的架构也采用的是主从结构:即master-slaves架构,一个JobTracker带多个TaskTracker

    超哥的杂货铺

扫码关注腾讯云开发者

领取腾讯云代金券