Tomcat启动流程简析

Tomcat是一款我们平时开发过程中最常用到的Servlet容器。本系列博客会记录Tomcat的整体架构、主要组件、IO线程模型、请求在Tomcat内部的流转过程以及一些Tomcat调优的相关知识。

力求达到以下几个目的:

  • 更加熟悉Tomcat的工作机制,工作中遇到Tomcat相关问题能够快速定位,从源头来解决;
  • 对于一些高并发场景能够对Tomcat进行调优;
  • 通过对Tomcat源码的分析,吸收一些Tomcat的设计的理念,应用到自己的软件开发过程中。

1. Bootstrap启动入口

在前面分析Tomcat启动脚本的过程中,我们最后发现startup.bat最后是通过调用Bootstrap这个类的main方法来启动Tomcat的,所以先去看下Bootstrap这个类。

public static void main(String args[]) {

        synchronized (daemonLock) {
            if (daemon == null) {
                // Don't set daemon until init() has completed
                Bootstrap bootstrap = new Bootstrap();
                try {
                    //创建Bootstrap对象,代用init方法
                    bootstrap.init();
                } catch (Throwable t) {
                    handleThrowable(t);
                    t.printStackTrace();
                    return;
                }
                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")) {
                //一般情况下会进入这步,调用Bootstrap对象的load和start方法。
                //将Catalina启动设置成block模式
                daemon.setAwait(true);
                daemon.load(args);
                daemon.start();
                if (null == daemon.getServer()) {
                    System.exit(1);
                }
            } 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);
        }
    }

上面的代码逻辑比较简单,如果我们正常启动tomcat,会顺序执行Bootstrap对象的init()方法, daemon.setAwait(true)、daemon.load(args)和daemon.start()方法。我们先看下Bootstrap对象的init方法:

public void init() throws Exception {

        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");
        Class<?> startupClass = catalinaLoader.loadClass("org.apache.catalina.startup.Catalina");
        Object startupInstance = startupClass.getConstructor().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);
        method.invoke(startupInstance,paramValues);

        catalinaDaemon = startupInstance;

    }

这个方法主要做了以下几件事:

  • 创建commonLoader、catalinaLoader、sharedLoader类加载器(默认情况下这三个类加载器指向同一个对象。建议看看createClassLoader方法,里面做的事情还挺多,比如装载catalina.properties里配置的目录下的文件和jar包,后两个加载器的父加载器都是第一个,最后注册了MBean,可以用于JVM监控该对象);
  • 实例化一个org.apache.catalina.startup.Catalina对象,并赋值给静态成员catalinaDaemon,以sharedLoader作为入参通过反射调用该对象的setParentClassLoader方法。

执行完init()方法,就开始执行bootstrap对象的load和start方法;

 private void load(String[] arguments)
        throws Exception {

        // Call the load() method
        String methodName = "load";
        Object param[];
        Class<?> paramTypes[];
        if (arguments==null || arguments.length==0) {
            paramTypes = null;
            param = null;
        } else {
            paramTypes = new Class[1];
            paramTypes[0] = arguments.getClass();
            param = new Object[1];
            param[0] = arguments;
        }
        Method method =
            catalinaDaemon.getClass().getMethod(methodName,paramTypes);
        if (log.isDebugEnabled())
            log.debug("Calling startup class " + method);
        method.invoke(catalinaDaemon,param);

    }

调用catalinaDaemon对象的load方法,catalinaDaemon这个对象的类型是org.apache.catalina.startup.Catalina。strat方法也是类似的,最后都是调用Catalina的start方法。

总结下Bootstrap的启动方法最主要干的事情就是创建了Catalina对象,并调用它的load和start方法。

2. Catalina的load和start方法

第一节分析到Bootstrap会触发调用Catalina的load和start方法。

     /**
     * 从注释可以看出这个方法的作用是创建一个Server实例
     * Start a new server instance.
     */
    public void load() {
        if (loaded) {
            return;
        }
        loaded = true;
        long t1 = System.nanoTime();
        //检查临时目录
        initDirs();
        // Before digester - it may be needed
        initNaming();

        // Create and execute our Digester
        Digester digester = createStartDigester();

        InputSource inputSource = null;
        InputStream inputStream = null;
        File file = null;
        try {
            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);
                }
            }
            if (inputStream == null) {
                try {
                    inputStream = getClass().getClassLoader()
                        .getResourceAsStream(getConfigFile());
                    inputSource = new InputSource
                        (getClass().getClassLoader()
                         .getResource(getConfigFile()).toString());
                } catch (Exception e) {
                    if (log.isDebugEnabled()) {
                        log.debug(sm.getString("catalina.configFail",getConfigFile()),e);
                    }
                }
            }

            // This should be included in catalina.jar
            // Alternative: don't bother with xml,just create it manually.
            if (inputStream == null) {
                try {
                    inputStream = getClass().getClassLoader()
                            .getResourceAsStream("server-embed.xml");
                    inputSource = new InputSource
                    (getClass().getClassLoader()
                            .getResource("server-embed.xml").toString());
                } catch (Exception e) {
                    if (log.isDebugEnabled()) {
                        log.debug(sm.getString("catalina.configFail","server-embed.xml"),e);
                    }
                }
            }


            if (inputStream == null || inputSource == null) {
                if  (file == null) {
                    log.warn(sm.getString("catalina.configFail",getConfigFile() + "] or [server-embed.xml]"));
                } else {
                    log.warn(sm.getString("catalina.configFail",file.getAbsolutePath()));
                    if (file.exists() && !file.canRead()) {
                        log.warn("Permissions incorrect,read permission is not allowed on the file.");
                    }
                }
                return;
            }

            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 {
            if (inputStream != null) {
                try {
                    inputStream.close();
                } catch (IOException e) {
                    // Ignore
                }
            }
        }

        getServer().setCatalina(this);
        getServer().setCatalinaHome(Bootstrap.getCatalinaHomeFile());
        getServer().setCatalinaBase(Bootstrap.getCatalinaBaseFile());

        // Stream redirection
        initStreams();

        // Start the new server
        try {
            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);
            }
        }

        long t2 = System.nanoTime();
        if(log.isInfoEnabled()) {
            log.info("Initialization processed in " + ((t2 - t1) / 1000000) + " ms");
        }
    }

将上面的代码精简下:

Digester digester = createStartDigester();  
inputSource.setByteStream(inputStream);  
digester.push(this);  
digester.parse(inputSource);  
getServer().setCatalina(this);  
getServer().init();  

做的事情就两个:

  • 创建一个Digester对象(Digester对象的作用就是解析server.xml配置文件,这边会先加载conf/server.xml文件,找不到的话会尝试加载server-embed.xml这个配置文件),解析完成后生成org.apache.catalina.core.StandardServer、org.apache.catalina.core.StandardService、org.apache.catalina.connector.Connector、org.apache.catalina.core.StandardEngine、org.apache.catalina.core.StandardHost、org.apache.catalina.core.StandardContext等等一系列对象,这些对象从前到后前一个包含后一个对象的引用(一对一或一对多的关系)。最后将StandardServer赋值给Catalina对象的server属性;如果你配置了连接器组件共享的线程池,还会生成StandardThreadExecutor对象。
  • 第二件事就是调用StandardServer的init方法。

临时总结下:Catalina的load方法的作用主要是解析conf/server.xml,生成StandardServer对象,再触发StandardServer的init方法。

第一节中还分析到Bootstrap会触发调用Catalina的start方法。那么我们看看start方法中干了什么。

/**
     * Start a new server instance.
     */
    public void start() {

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

        if (getServer() == null) {
            log.fatal("Cannot start server. Server instance is not configured.");
            return;
        }

        long t1 = System.nanoTime();

        // Start the new server
        try {
            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;
        }

        long t2 = System.nanoTime();
        if(log.isInfoEnabled()) {
            log.info("Server startup in " + ((t2 - t1) / 1000000) + " ms");
        }

        // Register shutdown hook
        if (useShutdownHook) {
            if (shutdownHook == null) {
                shutdownHook = new CatalinaShutdownHook();
            }
            Runtime.getRuntime().addShutdownHook(shutdownHook);

            // If JULI is being used,disable JULI's shutdown hook since
            // shutdown hooks run in parallel and log messages may be lost
            // if JULI's hook completes before the CatalinaShutdownHook()
            LogManager logManager = LogManager.getLogManager();
            if (logManager instanceof ClassLoaderLogManager) {
                ((ClassLoaderLogManager) logManager).setUseShutdownHook(
                        false);
            }
        }

        if (await) {
            await();
            stop();
        }
    }

这段代码最主要的作用就是调用StandardServer对象的start方法。

总结下:Catalina对象的laod和start方法的作用是解析conf/server.xml,生成StandardServer对象,再触发StandardServer的init方法和start方法。

到这边为止我们可以看到Tomcat的启动流程还是很清晰的,下面继续看StandardServer的init方法和start到底干了些什么。

3.StandardServer的init和start方法

通过寻找StandardServer的init方法,我们发现StandardServer本身没有实现这个方法,这个方法是它从父类LifecycleBase中继承过来的:

@Override
    public final synchronized void init() throws LifecycleException {
        if (!state.equals(LifecycleState.NEW)) {
            invalidTransition(Lifecycle.BEFORE_INIT_EVENT);
        }

        try {
            //发布初始化容器时间,对应的listener做相应处理
            setStateInternal(LifecycleState.INITIALIZING,null,false);
            //调用子类的initInternal()
            initInternal();
            //发布容器已经初始化事件,对应的listener做相应处理
            setStateInternal(LifecycleState.INITIALIZED,false);
        } catch (Throwable t) {
            handleSubClassException(t,"lifecycleBase.initFail",toString());
        }
    }

所以调用StandardServer的init方法,其实是促发了容器初始化事件发布,然后又调到了StandardServer的initInternal方法。那么我们看看StandardServer的start方法的逻辑是什么。

代码点进去,发现StandardServer的start方法也是调的父类LifecycleBase中的方法。

@Override
    public final synchronized void start() throws LifecycleException {

        if (LifecycleState.STARTING_PREP.equals(state) || LifecycleState.STARTING.equals(state) ||
                LifecycleState.STARTED.equals(state)) {

            if (log.isDebugEnabled()) {
                Exception e = new LifecycleException();
                log.debug(sm.getString("lifecycleBase.alreadyStarted",toString()),e);
            } else if (log.isInfoEnabled()) {
                log.info(sm.getString("lifecycleBase.alreadyStarted",toString()));
            }

            return;
        }

        if (state.equals(LifecycleState.NEW)) {
            init();
        } else if (state.equals(LifecycleState.FAILED)) {
            stop();
        } else if (!state.equals(LifecycleState.INITIALIZED) &&
                !state.equals(LifecycleState.STOPPED)) {
            invalidTransition(Lifecycle.BEFORE_START_EVENT);
        }

        try {
            //发布事件
            setStateInternal(LifecycleState.STARTING_PREP,false);
            //调用子类的startInternal
            startInternal();
            if (state.equals(LifecycleState.FAILED)) {
                // This is a 'controlled' failure. The component put itself into the
                // FAILED state so call stop() to complete the clean-up.
                stop();
            } else if (!state.equals(LifecycleState.STARTING)) {
                // Shouldn't be necessary but acts as a check that sub-classes are
                // doing what they are supposed to.
                invalidTransition(Lifecycle.AFTER_START_EVENT);
            } else {
                //发布容器启动事件
                setStateInternal(LifecycleState.STARTED,false);
            }
        } catch (Throwable t) {
            // This is an 'uncontrolled' failure so put the component into the
            // FAILED state and throw an exception.
            handleSubClassException(t,"lifecycleBase.startFail",toString());
        }
    }

从以上init和start方法的定义可以看到这两个方法最终将会调用StandardServer中定义的initInternal和startInternal。

先来看initInternal方法

protected void initInternal() throws LifecycleException {

        super.initInternal();

        // Register global String cache
        // Note although the cache is global,if there are multiple Servers
        // present in the JVM (may happen when embedding) then the same cache
        // will be registered under multiple names
        onameStringCache = register(new StringCache(),"type=StringCache");

        // Register the MBeanFactory
        MBeanFactory factory = new MBeanFactory();
        factory.setContainer(this);
        onameMBeanFactory = register(factory,"type=MBeanFactory");

        // Register the naming resources
        globalNamingResources.init();

        // Populate the extension validator with JARs from common and shared
        // class loaders
        if (getCatalina() != null) {
            ClassLoader cl = getCatalina().getParentClassLoader();
            // Walk the class loader hierarchy. Stop at the system class loader.
            // This will add the shared (if present) and common class loaders
            while (cl != null && cl != ClassLoader.getSystemClassLoader()) {
                if (cl instanceof URLClassLoader) {
                    URL[] urls = ((URLClassLoader) cl).getURLs();
                    for (URL url : urls) {
                        if (url.getProtocol().equals("file")) {
                            try {
                                File f = new File (url.toURI());
                                if (f.isFile() &&
                                        f.getName().endsWith(".jar")) {
                                    ExtensionValidator.addSystemResource(f);
                                }
                            } catch (URISyntaxException e) {
                                // Ignore
                            } catch (IOException e) {
                                // Ignore
                            }
                        }
                    }
                }
                cl = cl.getParent();
            }
        }
        // Initialize our defined Services
        for (int i = 0; i < services.length; i++) {
            services[i].init();
        }
    }

重点代码在最后,循环调用了Service组件的init方法。

再来看StandardServer的startInternal方法

@Override
    protected void startInternal() throws LifecycleException {

        fireLifecycleEvent(CONFIGURE_START_EVENT,null);
        setState(LifecycleState.STARTING);

        globalNamingResources.start();

        // Start our defined Services
        synchronized (servicesLock) {
            for (int i = 0; i < services.length; i++) {
                services[i].start();
            }
        }
    }

也是循环调用了Service组件的start方法。这边的Service组件就是在从conf/server.xml中解析出来的StandardService对象,查看下这个类的继承体系:

LifecycleBase (org.apache.catalina.util)
    LifecycleMBeanBase (org.apache.catalina.util)
        StandardService (org.apache.catalina.core)

我们发现这个类继承体系和StandardServer是一样的。其实我们再观察的仔细一点会发现从conf/server.xml解析胡来的类的继承体系都是一样的。所以我们调用这些类的init和start方法最后还是会调用到他们的initInternal和startInternal方法。

4. StandardService的initInternal和startInternal方法

先看下StandardService的initInternal方法

@Override
    protected void initInternal() throws LifecycleException {

        super.initInternal();
        //调用engine的initInternal方法,这个方法中也没做特别重要的操作,只是做了一个getReal操作
        if (engine != null) {
            engine.init();
        }

        //StandardThreadExecutor的initInternal方法中没没干什么事情
        // Initialize any Executors
        for (Executor executor : findExecutors()) {
            if (executor instanceof JmxEnabled) {
                ((JmxEnabled) executor).setDomain(getDomain());
            }
            executor.init();
        }

        // Initialize mapper listener
        //这步也没做什么重要操作
        mapperListener.init();

        // Initialize our defined Connectors
        // 连接器主键的初始化,主要是检查连接器的protocolHandler的主键,并将其初始化.
        synchronized (connectorsLock) {
            for (Connector connector : connectors) {
                connector.init();
            }
        }
    }

看了上面的代码,觉得Tomcat源码逻辑还是很清晰的。之前在分析Tomcat组件的文章中讲到Service组件是有Connector组件、engine组件和一个可选的线程池组成。上面的代码中正好对应了这三个组件的初始化话。

Connector组件和engine组件的初始化又会触发他们各自子组件的初始化,所以StandardService的initInternal方法会触发Tomcat下各类组件的初始化。这边大致记录下各个组件初始化话的顺序:

  • engine组件初始化:engine组件初始化没做什么特别的操作,也没触发它的子组件(Host、Context和Wrapper组件的初始化),所以这步比较简单;
  • Executor组件的初始化:没有触发其他组件初始化;
  • Mapper组件初始化:mapper组件初始化也没干什么重要的操作,也没触发其他子组件初始化;
  • Connector组件初始化:检查连接器的protocolHandler的子组件,并触发其初始化
  • ProtocolHandler组件初始化:触发Endpoint组件初始化,Endpoint类才是接收转化请求的真正的类;

然后再看StandardService的startInternal方法

protected void startInternal() throws LifecycleException {

        if(log.isInfoEnabled())
            log.info(sm.getString("standardService.start.name",this.name));
        setState(LifecycleState.STARTING);

        // Start our defined Container first
        if (engine != null) {
            synchronized (engine) {
                engine.start();
            }
        }

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

        mapperListener.start();

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

逻辑依然很清楚,StandardService会依次触发各个子组件的start方法。

  • Engine组件的start:Engine组件的start方法组要作用还是触发了Host组件的start方法,具体代码见

    protected synchronized void startInternal() throws LifecycleException {  
      
        // Start our subordinate components,if any  
        if ((loader != null) && (loader instanceof Lifecycle))  
            ((Lifecycle) loader).start();  
        logger = null;  
        getLogger();  
        if ((manager != null) && (manager instanceof Lifecycle))  
            ((Lifecycle) manager).start();  
        if ((cluster != null) && (cluster instanceof Lifecycle))  
            ((Lifecycle) cluster).start();  
        Realm realm = getRealmInternal();  
        if ((realm != null) && (realm instanceof Lifecycle))  
            ((Lifecycle) realm).start();  
        if ((resources != null) && (resources instanceof Lifecycle))  
            ((Lifecycle) resources).start();  
      
        // 找出Engine的子容器,也就是Host容器
        Container children[] = findChildren();  
        List<Future<Void>> results = new ArrayList<Future<Void>>();  
        //利用线程池调用Host的start方法
        for (int i = 0; i < children.length; i++) {  
            results.add(startStopExecutor.submit(new StartChild(children[i])));  
        }  
      
        boolean fail = false;  
        for (Future<Void> result : results) {  
            try {  
                result.get();  
            } catch (Exception e) {  
                log.error(sm.getString("containerBase.threadedStartFailed"),e);  
                fail = true;  
            }  
      
        }  
        if (fail) {  
            throw new LifecycleException(  
                    sm.getString("containerBase.threadedStartFailed"));  
        }  
      
        // Start the Valves in our pipeline (including the basic),if any  
        if (pipeline instanceof Lifecycle)  
            ((Lifecycle) pipeline).start();  
        setState(LifecycleState.STARTING);  
        // Start our thread  
        threadStart();  
      
    } 
    
  • Host组件的start:经过前面介绍,我们知道Host组件的start方法最后还是会调用自己startInternal方法;

  • Context组件的start:触发Wrapper的start,加载filter、Servlet等;

  • Wrapper组件的start:

这边我们重点看下StandardContext的startInternal,这个方法干的事情比较多:

 protected synchronized void startInternal() throws LifecycleException {

        if(log.isDebugEnabled())
            log.debug("Starting " + getBaseName());

        // Send j2ee.state.starting notification
        if (this.getObjectName() != null) {
            Notification notification = new Notification("j2ee.state.starting",this.getObjectName(),sequenceNumber.getAndIncrement());
            broadcaster.sendNotification(notification);
        }

        setConfigured(false);
        boolean ok = true;

        // Currently this is effectively a NO-OP but needs to be called to
        // ensure the NamingResources follows the correct lifecycle
        if (namingResources != null) {
            namingResources.start();
        }

        // Post work directory
        postWorkDirectory();

        // Add missing components as necessary
        if (getResources() == null) {   // (1) Required by Loader
            if (log.isDebugEnabled())
                log.debug("Configuring default Resources");

            try {
                setResources(new StandardRoot(this));
            } catch (IllegalArgumentException e) {
                log.error(sm.getString("standardContext.resourcesInit"),e);
                ok = false;
            }
        }
        if (ok) {
            resourcesStart();
        }

        if (getLoader() == null) {
            WebappLoader webappLoader = new WebappLoader(getParentClassLoader());
            webappLoader.setDelegate(getDelegate());
            setLoader(webappLoader);
        }

        // An explicit cookie processor hasn't been specified; use the default
        if (cookieProcessor == null) {
            cookieProcessor = new Rfc6265CookieProcessor();
        }

        // Initialize character set mapper
        getCharsetMapper();

        // Validate required extensions
        boolean dependencyCheck = true;
        try {
            dependencyCheck = ExtensionValidator.validateApplication
                (getResources(),this);
        } catch (IOException ioe) {
            log.error(sm.getString("standardContext.extensionValidationError"),ioe);
            dependencyCheck = false;
        }

        if (!dependencyCheck) {
            // do not make application available if dependency check fails
            ok = false;
        }

        // Reading the "catalina.useNaming" environment variable
        String useNamingProperty = System.getProperty("catalina.useNaming");
        if ((useNamingProperty != null)
            && (useNamingProperty.equals("false"))) {
            useNaming = false;
        }

        if (ok && isUseNaming()) {
            if (getNamingContextListener() == null) {
                NamingContextListener ncl = new NamingContextListener();
                ncl.setName(getNamingContextName());
                ncl.setExceptionOnFailedWrite(getJndiExceptionOnFailedWrite());
                addLifecycleListener(ncl);
                setNamingContextListener(ncl);
            }
        }

        // Standard container startup
        if (log.isDebugEnabled())
            log.debug("Processing standard container startup");


        // Binding thread
        ClassLoader oldCCL = bindThread();

        try {
            if (ok) {
                // Start our subordinate components,if any
                Loader loader = getLoader();
                if (loader instanceof Lifecycle) {
                    ((Lifecycle) loader).start();
                }

                // since the loader just started,the webapp classloader is now
                // created.
                setClassLoaderProperty("clearReferencesRmiTargets",getClearReferencesRmiTargets());
                setClassLoaderProperty("clearReferencesStopThreads",getClearReferencesStopThreads());
                setClassLoaderProperty("clearReferencesStopTimerThreads",getClearReferencesStopTimerThreads());
                setClassLoaderProperty("clearReferencesHttpClientKeepAliveThread",getClearReferencesHttpClientKeepAliveThread());
                setClassLoaderProperty("clearReferencesObjectStreamClassCaches",getClearReferencesObjectStreamClassCaches());
                setClassLoaderProperty("skipMemoryLeakChecksOnJvmShutdown",getSkipMemoryLeakChecksOnJvmShutdown());

                // By calling unbindThread and bindThread in a row,we setup the
                // current Thread CCL to be the webapp classloader
                unbindThread(oldCCL);
                oldCCL = bindThread();

                // Initialize logger again. Other components might have used it
                // too early,so it should be reset.
                logger = null;
                getLogger();

                Realm realm = getRealmInternal();
                if(null != realm) {
                    if (realm instanceof Lifecycle) {
                        ((Lifecycle) realm).start();
                    }

                    // Place the CredentialHandler into the ServletContext so
                    // applications can have access to it. Wrap it in a "safe"
                    // handler so application's can't modify it.
                    CredentialHandler safeHandler = new CredentialHandler() {
                        @Override
                        public boolean matches(String inputCredentials,String storedCredentials) {
                            return getRealmInternal().getCredentialHandler().matches(inputCredentials,storedCredentials);
                        }

                        @Override
                        public String mutate(String inputCredentials) {
                            return getRealmInternal().getCredentialHandler().mutate(inputCredentials);
                        }
                    };
                    context.setAttribute(Globals.CREDENTIAL_HANDLER,safeHandler);
                }

                // Notify our interested LifecycleListeners
                fireLifecycleEvent(Lifecycle.CONFIGURE_START_EVENT,null);

                // Start our child containers,if not already started
                for (Container child : findChildren()) {
                    if (!child.getState().isAvailable()) {
                        child.start();
                    }
                }

                // Start the Valves in our pipeline (including the basic),// if any
                if (pipeline instanceof Lifecycle) {
                    ((Lifecycle) pipeline).start();
                }

                // Acquire clustered manager
                Manager contextManager = null;
                Manager manager = getManager();
                if (manager == null) {
                    if (log.isDebugEnabled()) {
                        log.debug(sm.getString("standardContext.cluster.noManager",Boolean.valueOf((getCluster() != null)),Boolean.valueOf(distributable)));
                    }
                    if ( (getCluster() != null) && distributable) {
                        try {
                            contextManager = getCluster().createManager(getName());
                        } catch (Exception ex) {
                            log.error("standardContext.clusterFail",ex);
                            ok = false;
                        }
                    } else {
                        contextManager = new StandardManager();
                    }
                }

                // Configure default manager if none was specified
                if (contextManager != null) {
                    if (log.isDebugEnabled()) {
                        log.debug(sm.getString("standardContext.manager",contextManager.getClass().getName()));
                    }
                    setManager(contextManager);
                }

                if (manager!=null && (getCluster() != null) && distributable) {
                    //let the cluster know that there is a context that is distributable
                    //and that it has its own manager
                    getCluster().registerManager(manager);
                }
            }

            if (!getConfigured()) {
                log.error(sm.getString("standardContext.configurationFail"));
                ok = false;
            }

            // We put the resources into the servlet context
            if (ok)
                getServletContext().setAttribute
                    (Globals.RESOURCES_ATTR,getResources());

            if (ok ) {
                if (getInstanceManager() == null) {
                    javax.naming.Context context = null;
                    if (isUseNaming() && getNamingContextListener() != null) {
                        context = getNamingContextListener().getEnvContext();
                    }
                    Map<String,Map<String,String>> injectionMap = buildInjectionMap(
                            getIgnoreAnnotations() ? new NamingResourcesImpl(): getNamingResources());
                    setInstanceManager(new DefaultInstanceManager(context,injectionMap,this,this.getClass().getClassLoader()));
                }
                getServletContext().setAttribute(
                        InstanceManager.class.getName(),getInstanceManager());
                InstanceManagerBindings.bind(getLoader().getClassLoader(),getInstanceManager());
            }

            // Create context attributes that will be required
            if (ok) {
                getServletContext().setAttribute(
                        JarScanner.class.getName(),getJarScanner());
            }

            // Set up the context init params
            mergeParameters();

            // Call ServletContainerInitializers
            for (Map.Entry<ServletContainerInitializer,Set<Class<?>>> entry :
                initializers.entrySet()) {
                try {
                    entry.getKey().onStartup(entry.getValue(),getServletContext());
                } catch (ServletException e) {
                    log.error(sm.getString("standardContext.sciFail"),e);
                    ok = false;
                    break;
                }
            }

            // Configure and call application event listeners
            if (ok) {
                if (!listenerStart()) {
                    log.error(sm.getString("standardContext.listenerFail"));
                    ok = false;
                }
            }

            // Check constraints for uncovered HTTP methods
            // Needs to be after SCIs and listeners as they may programmatically
            // change constraints
            if (ok) {
                checkConstraintsForUncoveredMethods(findConstraints());
            }

            try {
                // Start manager
                Manager manager = getManager();
                if (manager instanceof Lifecycle) {
                    ((Lifecycle) manager).start();
                }
            } catch(Exception e) {
                log.error(sm.getString("standardContext.managerFail"),e);
                ok = false;
            }

            // Configure and call application filters
            if (ok) {
                if (!filterStart()) {
                    log.error(sm.getString("standardContext.filterFail"));
                    ok = false;
                }
            }

            // Load and initialize all "load on startup" servlets
            if (ok) {
                if (!loadOnStartup(findChildren())){
                    log.error(sm.getString("standardContext.servletFail"));
                    ok = false;
                }
            }

            // Start ContainerBackgroundProcessor thread
            super.threadStart();
        } finally {
            // Unbinding thread
            unbindThread(oldCCL);
        }

        // Set available status depending upon startup success
        if (ok) {
            if (log.isDebugEnabled())
                log.debug("Starting completed");
        } else {
            log.error(sm.getString("standardContext.startFailed",getName()));
        }

        startTime=System.currentTimeMillis();

        // Send j2ee.state.running notification
        if (ok && (this.getObjectName() != null)) {
            Notification notification =
                new Notification("j2ee.state.running",sequenceNumber.getAndIncrement());
            broadcaster.sendNotification(notification);
        }

        // The WebResources implementation caches references to JAR files. On
        // some platforms these references may lock the JAR files. Since web
        // application start is likely to have read from lots of JARs,trigger
        // a clean-up now.
        getResources().gc();

        // Reinitializing if something went wrong
        if (!ok) {
            setState(LifecycleState.FAILED);
        } else {
            setState(LifecycleState.STARTING);
        }
    }

上面的代码有4处重点:调用ServletContainerInitializers、启用Listener、启用Filter和启用startup的Servlet。这个和我们平时对Tomcat启动流程的认知是一致的。

到这里整个Container组件(包括Engine、Host、Context和Wrapper组件)的start方法调用就结束了。接下来是Connector和Mapper组件的start。

//MapperListenner的startInternal
public void startInternal() throws LifecycleException {

        setState(LifecycleState.STARTING);

        Engine engine = service.getContainer();
        if (engine == null) {
            return;
        }

        findDefaultHost();

        addListeners(engine);

        Container[] conHosts = engine.findChildren();
        for (Container conHost : conHosts) {
            Host host = (Host) conHost;
            if (!LifecycleState.NEW.equals(host.getState())) {
                // Registering the host will register the context and wrappers
                registerHost(host);
            }
        }
    }

以上方法的主要作用是将Host组件和域名映射起来。

最后看下Connector组件的start:

protected void startInternal() throws LifecycleException {

        // Validate settings before starting
        if (getPortWithOffset() < 0) {
            throw new LifecycleException(sm.getString(
                    "coyoteConnector.invalidPort",Integer.valueOf(getPortWithOffset())));
        }

        setState(LifecycleState.STARTING);

        try {
            //促发protocolHandler组件的start,最后促发endpoint组件的start
            //触发endpoint时会建立exceutor线程池,默认的话核心线程数10,最大线程数200
            //建立poller线程,最大是2个线程,如果你机器cpu的核数小于2的话就建立1个
            //建立accetpor线程,默认是1个(可以看看Acceptor这个类的源代码,了解下怎么接收请求的)
            protocolHandler.start();
        } catch (Exception e) {
            throw new LifecycleException(
                    sm.getString("coyoteConnector.protocolHandlerStartFailed"),e);
        }
    }

通过以上一些列复杂的调用过程,最终执行完所有在server.xml里配置的节点的实现类中initInternal和startInternal方法。上面提到的org.apache.catalina.core.StandardServer、org.apache.catalina.core.StandardService、org.apache.catalina.connector.Connector、org.apache.catalina.core.StandardEngine、org.apache.catalina.core.StandardHost、org.apache.catalina.core.StandardContext等等组件的这两个方法都会调用到。

至此,Tomcat已经能开始响应浏览器发过来的请求了。至于具体的Tomcat响应请求流程会在后续博客中介绍。

5. 总结

看了整个启动流程,虽然逻辑是比较清楚的,但是流程比较上,所以有必要做下总结:

  • step1:Bootstrap作为整个Tomcat主启动类,最主要的功能是创建Catalina对象,并调用它的load和start方法;

  • step2:Catalina的load方法的作用主要是解析conf/server.xml,生成StandardServer对象(此时生成StandardServer对象中已经包含了各种子组件,比如StandardService、StandardEngine等),再触发StandardServer的init方法;Catalina的start方法又触发了StandardServer的start方法;

  • step3:StandardServer的init方法和start方法会依次触发各个子组件的initInternal和startInternal方法。大致的触发顺序是:

    Engine组件的initInternal(这边要注意的是Engine组件并没有触发它的子组件Host、Context和Wrapper的initInternal)-->Executor组件initInternal(处理请求的工作线程池)-->Mapper组件初始化(mapper组件初始化也没干什么重要的操作,也没触发其他子组件初始化)-->Connector组件初始化(检查连接器的protocolHandler的子组件,并触发其初始化)-->ProtocolHandler组件初始化(触发Endpoint组件初始化)​

​ Engine组件的startInternal(主要作用是触发Host组件的start)-->Host组件的startInternal(主要作用是触发Context组件的startInternal)-->Contextz组件的startInternal(加载调用ServletContainerInitializers、加载Listener、加载filtr和startup的Servlet,并且触发Wrapper组件的startInternal)-->Wrapper组件的startInternal(加载映射Servlet)-->Mapper组件的startInternal(将域名和Host组件映射起来)-->Connector组件的startInternal(protocolHandler组件的start,最后促发endpoint组件的start)

参考

原文地址:https://www.cnblogs.com/54chensongxia

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


&lt;servlet&gt; &lt;servlet-name&gt;tomcatpooljsp&lt;/servlet-name&gt; &lt;jsp-file&gt;/WEB-INF/tomcatpool.jsp&lt;/jsp-file&gt; &lt;/servlet&gt; &lt;servlet-mapping&gt; &lt;servlet-name&gt;tomcatpooljsp&lt;/servlet-nam...
遵循Java Servlet 规范第4节中的建议 ,Apache Tomcat实现了系统地重新加载Java类的方法,以允许在不重新启动整个服务器的情况下更新应用程序的组件。 此功能对于开发非常重要,因为事实证明,随着服务器启动和重启时间的延长,这会严重浪费开发人员的时间。实际上,Java EE堆栈应用服务器的服务器重新启动时间很慢,这是Tomcat广泛用于个人和企业级项目的推动力之一。但是,即使Tomcat也无法 像运行时重新加载应用程序一样快地启动。通过仅重新加载隔离的应用程序的更改的类,开发人员..
JMX(Java管理扩展)是一项非常强大的技术,可让您管理,监视和配置Tomcat MBean。如果您是Tomcat管理员,那么您应该熟悉如何在tomcat中启用JMX来监视堆内存,线程,CPU使用率,类以及配置各种MBean。在本文中,我将讨论如何使用JConsole启用并连接到Tomcat。我假设您已经安装了Tomcat(如果没有);您可以参考安装指南。转到安装了Tomcat的路径 转到bin文件夹 将文件创建为“ setenv.sh” 使用vi编辑器修改文件并添加以下内容
总览介绍 建立 取得Java 获取TomCat 将TomCat安装为Windows服务 将TomCat设置为Linux服务(系统化) 使用Nginx作为反向代理 基本用法 手动启动和停止TomCat 验证TomCat服务器正在运行 服务静态文件 服务Java服务器页面(JSP) 修改设定 部署网络应用 使用管理网页界面 创建一个TomCat管理员用户 访问管理网络应用 管理网络应用 结论 参考链接介绍在最简单的概念中,To.
PSI Probe是Lambda Probe的社区驱动分支,使用相同的开源许可证(GPLv2)分发。它旨在替换和扩展Tomcat Manager,从而使管理和监视Apache Tomcat实例更加容易。与许多其他服务器监视工具不同,PSI Probe不需要对现有应用程序进行任何更改。它通过可访问Web的界面提供所有功能,只需将其部署到服务器即可使用。这些功能包括:请求:即使在每个应用程序的基础上,实时监视流量。 会话:浏览/搜索属性,查看上一个IP,到期,估计大小。 JSP:浏览,查看源代码,进
监视和管理Tomcat目录介绍 启用JMX远程 使用JMX远程Ant任务管理Tomcat JMXAccessorOpenTask-JMX打开连接任务 JMXAccessorGetTask:获取属性值Ant任务 JMXAccessorSetTask:设置属性值Ant任务 JMXAccessorInvokeTask:调用MBean操作Ant任务 JMXAccessorQueryTask:查询MBean Ant任务 JMXAccessorCreateTask:远程创建MBean Ant任
1.tomcat与jetty都是一种servlet引擎,他们都支持标准的servlet规范和javaEE规范
“The origin server did not find a current representation for the target resource...
Artifacts是maven中的一个概念,表示某个module要如何打包,例如war exploded、war、jar、ear等等这种打包形式;
使用 IDEA 编辑器开发项目十分便捷,这里介绍使用 IDEA 编辑器添加 Tomcat
这篇“servlet和tomcat的知识点有哪些”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅...
这篇文章主要讲解了“Tomcat管理平台实例分析”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“Tomcat管理平...
本文小编为大家详细介绍“tomcat虚拟主机怎么配置”,内容详细,步骤清晰,细节处理妥当,希望这篇“tomcat虚拟主机怎么配置”文章能帮助大家解决疑惑,下面跟
今天小编给大家分享一下tomcat相关配置与eclipse集成的方法的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家
这篇“Tomcat之web应用的目录组成结构是怎样的”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,
今天小编给大家分享一下tomcat目录映射的方法的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大...
这篇“tomcat的环境怎么配置”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文...
环境:tomcat:apache-tomcat-7.0.35 cactiEZ:10.1系统:centos5.6_x64一、配置tomcat服务器1、添加账号vim tomcat-users.xml 重启tomcat2、安装snmp协议yum...
一、 软环下载地址软件链接地址https://files.cnblogs.com/files/jinrf/openssl-1.0.2-latest.tar.gzhttps://files.cnblogs.com/files/jinrf/apr-util-1.6...