主頁 > 後端開發 > Tomcat原始碼分析--啟動流程

Tomcat原始碼分析--啟動流程

2020-10-21 11:52:34 後端開發

1.概述

要想了解Tomcat的啟動流程,必須先弄明白Tomcat有哪些組件,而對于Tomcat組件的層級結構了解,我們必須弄明白Tomcat一個最重要的組態檔“server.xml”,如果有過Tomcat調優經驗或者對Tomat有一定了解的話,一定知道這個檔案,他位于${tomcat.base}/conf/server.xml,通過這個檔案,我們可以配給幾乎所有tomcat的引數資訊(當然不會包括jvm相關的引數),那么我們先看一下server.xml的真實面目,為了更清楚的展現Tomcat組件,我把server.xml中的注釋部分全部洗掉了,

<?xml version="1.0" encoding="UTF-8"?>
<Server port="8005" shutdown="SHUTDOWN">
  <Listener className="org.apache.catalina.startup.VersionLoggerListener" />
  <Listener className="org.apache.catalina.core.AprLifecycleListener" SSLEngine="on" />
  <Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener" />
  <Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener" />
  <Listener className="org.apache.catalina.core.ThreadLocalLeakPreventionListener" />

  <GlobalNamingResources>
    <Resource name="UserDatabase" auth="Container"
              type="org.apache.catalina.UserDatabase"
              description="User database that can be updated and saved"
              factory="org.apache.catalina.users.MemoryUserDatabaseFactory"
              pathname="conf/tomcat-users.xml" />
  </GlobalNamingResources>

  <Service name="Catalina">
    <Executor name="tomcatThreadPool" namePrefix="catalina-exec-"
        maxThreads="150" minSpareThreads="4"/>

    <Connector executor="tomcatThreadPool" port="8080" protocol="HTTP/1.1"
               connectionTimeout="20000"
               redirectPort="8443" />

    <Engine name="Catalina" defaultHost="localhost">
      <Realm className="org.apache.catalina.realm.LockOutRealm">
        <Realm className="org.apache.catalina.realm.UserDatabaseRealm"
               resourceName="UserDatabase"/>
      </Realm>

      <Host name="localhost"  appBase="webapps"
            unpackWARs="true" autoDeploy="true">
        <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
               prefix="localhost_access_log" suffix=".txt"
               pattern="%h %l %u %t &quot;%r&quot; %s %b" />
        <Context docBase="webapps/test" path="/test"  reloadable="true" />
      </Host>
    </Engine>
  </Service>
</Server>

需要指出的是,server.xml組態檔中并沒有<Context>元素標簽,不是說這里不可以用這個標簽,而是Tomcat的官方檔案并不提議通過這種方式設定應用程式的位置,在http://ip:8080/docs/config/context.html中有如下一段話:

這段話的開頭就說明 了并不建議直接在server.xml檔案中添加<Context>元素,當然,感興趣的可以往后面看看原因,說了這么多,看到上面的按個xml其實還是有些犯怵,這么多,目錄結構哪有那么清晰,為了方便大家掌握,我有做了一個目錄結構圖:

在上圖中,Protocol Handler并不是server.xml的一個元素,但是他通過<Connector>中的protocol屬性配置的,默認HTTP/1.1,然后針對不同版本的tomat(比如tomcat7和tomcatd8),默認的協議處理器是不一樣的,下方代碼塊是取自tomcat8.5.59,

/**
 * Tomcat8.5.59
 *org.apache.catalina.connector.Connector#setProtocol
 */
public void setProtocol(String protocol) {
	boolean aprConnector = AprLifecycleListener.isAprAvailable() &&
			AprLifecycleListener.getUseAprConnector();

	if ("HTTP/1.1".equals(protocol) || protocol == null) {
		if (aprConnector) {
			setProtocolHandlerClassName("org.apache.coyote.http11.Http11AprProtocol");
		} else {
			setProtocolHandlerClassName("org.apache.coyote.http11.Http11NioProtocol");
		}
	} else if ("AJP/1.3".equals(protocol)) {
		if (aprConnector) {
			setProtocolHandlerClassName("org.apache.coyote.ajp.AjpAprProtocol");
		} else {
			setProtocolHandlerClassName("org.apache.coyote.ajp.AjpNioProtocol");
		}
	} else {
		//決定可以直接輸入類名
		setProtocolHandlerClassName(protocol);
	}
}

如下代碼塊取自Tomcat7..0.103,

/**
 * Tomcat7.0.103
 *org.apache.catalina.connector.Connector#setProtocol
 */
public void setProtocol(String protocol) {
	if (AprLifecycleListener.isAprAvailable()) {
		if ("HTTP/1.1".equals(protocol)) {
			setProtocolHandlerClassName
			("org.apache.coyote.http11.Http11AprProtocol");
		} else if ("AJP/1.3".equals(protocol)) {
			setProtocolHandlerClassName
			("org.apache.coyote.ajp.AjpAprProtocol");
		} else if (protocol != null) {
			setProtocolHandlerClassName(protocol);
		} else {
			setProtocolHandlerClassName
			("org.apache.coyote.http11.Http11AprProtocol");
		}
	} else {
		if ("HTTP/1.1".equals(protocol)) {
			setProtocolHandlerClassName
			("org.apache.coyote.http11.Http11Protocol");
		} else if ("AJP/1.3".equals(protocol)) {
			setProtocolHandlerClassName
			("org.apache.coyote.ajp.AjpProtocol");
		} else if (protocol != null) {
			setProtocolHandlerClassName(protocol);
		}
	}
}

通過上面兩個代碼塊,我們清楚的指導Tomcat8.5.59配置的類為org.apache.coyote.http11.Http11NioProtocol,Tomcat7.0.103中配置的類org.apache.coyote.http11.Http11Protocol,所以Tomcat8.5.59采用同步非阻塞的方式,而Tomcat7.0.103則采用同步阻塞的方式,

到目前為止,我們了解了Tomcat的主要組件以及層次結構,那么接下來我們就進入Tomcat的原始碼世界,一起揭開他的神秘面紗,

2. 流程時序圖

為了進一步更好的理解Tomat的啟動流程,我們先來一張時序圖,心里先有個粗略的印象,需要注意的事,要注意每一步的序號,接下來我是按照序號進行講解的,

3.啟動流程原始碼分析

通過上面的時序圖,我們可以看出來Tomcat啟動主要就是三步,Tomcat環境的初始化、Tomcat組件的各個init方法以及Tomcat組件的各個start方法,由于Tomcat的各個組件都要記錄到生命周期中,所以基本被org.apache.catalina.util. LifecycleMBeanBase的init和start方法包裹,真正的實作是以Standard開頭的類的以Internal結尾的發方法中,比如,service的init方法的具體實作為org.apache.catalina.core.StandardService#initInternal,接下里,我們就以Tomcat的入口類org.apache. catalina.startup.Bootstrap(以下簡稱"Bootstrap")作為切入點,進行一步一步的詳細分析,

3.1 Tomcat環境初始化(0:static)

通過0:static這個關鍵字,相信大家已經猜出這一部分代碼主要是在哪執行的吧?沒錯,是Tomcat啟動類Bootstrap的靜態代碼塊中,針對這段代碼塊,主要實作一個功能,初始化catalinaHomeFile和catalinaBaseFile兩個變數,便于尋找Tomcat涉及到的組態檔、classpath等路徑,針對catalinaHomeFile變數,尋找的優先級為:環境變數catalina.home->包含bootsrap.jar的父級目錄->程式運行的當前路徑System.getProperty("user.dir");針對catalinaBaseFile變數,尋找的優先級為:環境變數catalina.base->catalinaHomeFile,

3.2 Bootstap物件的初始化(1.1:init)

在方法org.apache.catalina.startup.Bootstrap#init()中,主要做了如下幾件事:

1)初始化Tomcat的三大類加載器(commonLoader->catalinaLoader->sharedLoader),

類加載的初始化主要依賴于組態檔${tomcat.base}/conf/catalina.properties中的common.loader、server.loader和shared.loader,默認情況下,common.loader會有值,commonLoader作為jvm中AppClassLoader的子加載器,主要增加加載Tomcat下的的jar或者class檔案,而server.loader和shared.loader屬性配置為空,將commonLoader作為自己的加載器,也就是說,默認情況下,AppClassLoader有個子加載器commonLoader,commonLoader有一個與其相同的子加載器catalinaLoader,catalinaLoader有一個與其相同的子加載器sharedLoader,

common.loader="${catalina.base}/lib","${catalina.base}/lib/*.jar","${catalina.home}/lib","${catalina.home}/lib/*.jar"
server.loader=
shared.loader=

2)初始化Catalina類,

Catalina作為Tomcat副總級別的人物,對于Tomcat有著極其重要的作用,Bootstrap其實沒有做多少作業,主要的初始化作業包括server.xml檔案的決議、server組件的初始化都是在此類中進行的,為了便于擴展,此處采用了反射的方式進行初始化此類,最終,將Catalina物件賦值給catalinaDaemon ,

public void init() throws Exception {
	...
	//通過反射的方式初始化Catalina類,并將最底層的類加載器sharedLoader賦值給物件的父加載器
	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);

	//Catalina作為守護執行緒
	catalinaDaemon = startupInstance;
}

這里針對類加載器需要注意的事

1)Catalina并沒有使用末級加載器sharedLoader加載此類,而是使用其父加載器catalina加載,

2)Catalina有一個屬性parentClassLoader,并將shareLoader賦值給他,而這也將作為應用部署的父類加載器,

3.3 組件的初始化(1.2:load)

在Bootstrap#main中,通過呼叫Bootstrap#load方法,此方法仍然通過反射的方式呼叫Catalina的load方法,

private void load(String[] arguments) throws Exception {
	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);
}

這個類沒什么可說了,很容易理解,那么接下來進入我們的重頭戲,從Catalina的load方法開始,尋求Tomcat是怎么初始化他的各個組件的,

3.3.1 Catalina的加載(1.2.1:load)

Catalina加載在方法org.apache.catalina.startup.Catalina#load()中執行,在此方法中,主要完成了以下三件事:

1)決議server.xml檔案,并將server物件賦值給org.apache.catalina.startup.Catalina#server,

2)重新定向System.out流,

3)server組件的初始化

3.3.1.1 決議server.xml

server.xml決議采用SAXParser決議器加創建決議規則的方式進行決議,代碼開始通過createStartDigester()方法創建Digester,什么是Digester呢?

A Digester processes an XML input stream by matching a series of element nesting patterns to execute Rules that have been added prior to the start of parsing. This package was inspired by the XmlMapper class that was part of Tomcat 3.0 and 3.1, but is organized somewhat differently.

Digester實作了SAXParser默認的處理器DefaultHandler2,通過添加一系列規則,實作server.xml檔案的決議,而這些規則要繼承org.apache.tomcat.util.digester.Rule類,主要實作了begin(namespace,name,attributes)和end(namespace, name)方法,主要完成SAXParser在決議到當前元素開始元素標記的業務邏輯(eg:<sever>)和決議結束元素標記(eg:</sever>),在Tomcat原始碼中,主要的規則包括org.apache.tomcat.util.digester.ObjectCreateRule、org.apache.catalina.startup.ConnectorCreateRule和org.apache.catalina.startup.SetAllPropertiesRule,其中ObjectCreateRule處理除了Connector之外幾乎所有的元素,ConnectorCreateRule主要針對Connector標簽進行決議,SetAllPropertiesRule值用于將其他節點的屬性復制到當前節點相同的屬性名稱上,

digester.addRule("Server/Service/Connector",
                         new ConnectorCreateRule());
        digester.addRule("Server/Service/Connector",
                         new SetAllPropertiesRule(new String[]{"executor", "sslImplementationName"}));
        digester.addSetNext("Server/Service/Connector",
                            "addConnector",
                            "org.apache.catalina.connector.Connector");

比如,看上面代碼,針對相同的標簽Server/Service/Connector,添加了兩個規則,

上面還說到server物件賦值給org.apache.catalina.startup.Catalina#server,那么這一步是怎么設定的呢?如果我們通過查看org.apache.catalina.startup.Catalina#setServer()被哪些物件應用,根本是查不到的,好吧,他也是通過規則設定的,

digester.addObjectCreate("Server","org.apache.catalina.core.StandardServer","className");
digester.addSetProperties("Server");
digester.addSetNext("Server","setServer","org.apache.catalina.Server");

3.3.1.2 重新定向System.out流

這一步不難理解,將System.out和System.err重定向到org.apache.tomcat.util.log.SystemLogHandler上,SystemLogHandler又是一個什么鬼?

/**
 * This helper class may be used to do sophisticated redirection of
 * System.out and System.err on a per Thread basis.
 * 可以使用該幫助程式類在每個執行緒的基礎上對System.out和System.err進行復雜的重定向,
 *
 * A stack is implemented per Thread so that nested startCapture
 * and stopCapture can be used.
 *
 * @author Remy Maucherat
 * @author Glenn L. Nielsen
 */

我們知道,System.out他是一個靜態的輸出流,是一個被所有執行緒共享的物件,而tomcat啟動了一大堆執行緒,為了保證縣城的安全,需要通過SystemLogHandler包裝一下,在這個類中,通過ThreadLocal(執行緒區域變數)保證了各個執行緒System.out的獨立性,

3.3.1.3 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);
	}
}

server初始化放在下一章節講解,這里只需明白呼叫的server的init()方法即可,

3.3.2 server初始化(1.2.2:init)

在這個方法中,并沒有做太多實質性的作業,主要是通過回圈遍歷services實作service的初始化,

// Initialize our defined Services
for (Service service : services) {
	service.init();
}

3.3.3 service初始化(1.2.3:init)

由于Tomcat的各個組件都有生命周期的概念,其無論是容器類組件還是非容器類組件都繼承了org.apache.catalina.util. LifecycleBase,而LifecycleBase又實作了org.apache.catalina.Lifecycle,對于所有繼承LifecycleBase的組件,通過呼叫org.apache.catalina.util.LifecycleBase#init方法,改變當前組件的狀態,而真實的實作方法為org.apache.catalina.util. LifecycleBase#initInternal(內部的初始化方法),通過下面代碼可以知道,首先這個方法是執行緒安全的,其次這個方法使用final修飾,不能重寫,這也就是避免組件的組件的狀態被隨意篡改,

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

	try {
		setStateInternal(LifecycleState.INITIALIZING, null, false);
		initInternal();
		setStateInternal(LifecycleState.INITIALIZED, null, false);
	} catch (Throwable t) {
		handleSubClassException(t, "lifecycleBase.initFail", toString());
	}
}

接下來,我們重點查看initInternal方法,當我們點進這個方法,發現是一個抽象方法,真正的實作交給子類,這里使用了模板方法的設計模式,當我們按ctrl+alt+B,發現實作他的有一大堆,此時不要驚慌,現在我們分析的事service方法,所以我們找和service相關的實作類,終于我們找到StandardService,

以后找Tomat組件實作類的時候,基本以Standard開頭,除了Connector,

@Override
protected void initInternal() throws LifecycleException {
	super.initInternal();
	//初始化執行引擎
	if (engine != null) {
		engine.init();
	}
	// 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
	synchronized (connectorsLock) {
		for (Connector connector : connectors) {
			try {
				connector.init();
			} catch (Exception e) {
				
			}
		}
	}
}

通過上面的代碼,我們知道此方法主要完成了三件事情:

1)初始化執行引擎,

2)初始化執行器(執行緒池),如果配置了,

3)初始化連接器,

詳細的執行程序后面會有講解,這里我們只是了解一個大體的結構,想一想,這里的初始化程序是否可以顛倒?

3.3.4 Engine初始化(1.2.4:init)

代碼的開始還是一如既往的初始化當前組件的狀態,然后通過呼叫initInternal方法實作組件的初始化,所以我們重點看一下org.apache.catalina.core.StandardEngine#initInternal方法,當我們進入此方法,發現只有兩行代碼,

@Override
protected void initInternal() throws LifecycleException {
	// Ensure that a Realm is present before any attempt is made to start
	// one. This will create the default NullRealm if necessary.
	getRealm();
	super.initInternal();
}

這里我們重點看super.initInternal()方法,org.apache.catalina.core.ContainerBase#initInternal,這是容器基本類的初始化方法,

@Override
protected void initInternal() throws LifecycleException {
	BlockingQueue<Runnable> startStopQueue = new LinkedBlockingQueue<>();
	startStopExecutor = new ThreadPoolExecutor(
			getStartStopThreadsInternal(),
			getStartStopThreadsInternal(), 10, TimeUnit.SECONDS,
			startStopQueue,
			new StartStopThreadFactory(getName() + "-startStop-"));
	startStopExecutor.allowCoreThreadTimeOut(true);
	super.initInternal();
}

通過上面的代碼,我們了解到執行引擎Engine主要初始化一個執行緒池startStopExecutor,不知道大家是否用過阿里的代碼檢查工具,當我們使用 Executors.newSingleThreadExecutor()創建執行緒池的時候就會報錯,這里就是一個典范,大家有時間可以學習一下,

3.3.5 Executor初始化(1.2.5:init)

這個方法的初始化也十分簡單,并沒有做什么事,無非就是設定個狀態什么的,

3.3.6 Connector初始化(1.2.6:init)

按照常理,我們依舊進入org.apache.catalina.connector.Connector#initInternal方法,這個Connector并沒有按照之前那種以Standard開頭,這就是一個實作類,在這個方法中,主要完成如下幾件事:

1)創建CoyoteAdapter,這個類主要用于將http請求封裝成request、response物件

2)將CoyoteAdapter賦值給協議處理器protocolHandler

3)protocolHandler初始化

在這里,想一想protocolHandler是什么時候初始化的?

3.3.7 ProtocolHandler初始化(1.2.7:init)

protocolHandler.init();

->org.apache.coyote.http11.AbstractHttp11Protocol#init

->org.apache.coyote.AbstractProtocol#init

通過如下的跟蹤路徑,我們找到了endPoint,這里重點看一下endPoint初始化org.apache.tomcat.util.net.AbstractEndpoint #init,在這個方法中,我們看到有個bind()的方法,我們點進入,發現有三個實作,AprEndpoint、Nio2EndPoint和NioEndpoint,

這里簡單提一下Nio2Endpoint和NioEndpoint,NioEndpoint屬于同步非阻塞類,而Nio2Endpoint屬于異步非阻塞類,這也就決定了NioEndpoint的請求呼叫鏈為acceptor->poller->執行緒池;而Nio2Endpoint的請求呼叫路徑為acceptor->執行緒池,這里可以想一下為什么Nio2Endpoint少了poller的呼叫,

當然,針對于當前版本的tomcat(8.5.59),默認的還是使用NioEndpoint,這個第一章有簡單介紹,剩下的時間就是自己捋代碼找到這個位置,在org.apache.tomcat.util.net.NioEndpoint#bind方法中,規規矩矩寫了一遍NioSocket編程,不熟悉Socket編程的借此機會可以熟悉一下,

到此位置,Tomcat各組件的初始化作業算是告一段落,接下來,就是真正的Tomcat啟動了,

3.4 組件的啟動(1.3:start)

到目前為止,我們逐漸熟悉了Tomcat的套路了,這里仍然是通過反射的方式呼叫Catalina的start方法,

public void start() throws Exception {
	if (catalinaDaemon == null) {
		init();
	}

	Method method = catalinaDaemon.getClass().getMethod("start", (Class [])null);
	method.invoke(catalinaDaemon, (Object [])null);
}

3.4.1 Catalina啟動(1.3.1:start)

Catalina啟動主要完成了三件事:

1)server組件的啟動;

2)設定關閉程式的鉤子方法;

3)開啟一個執行緒,用于監聽8005埠

第一件事我們稍后再說,現在我們主要說一下第二、第三件事,

3.4.1.1 設定關閉程式的鉤子方法

在這里,使用了jdk的一個方法Runtime.getRuntime().addShutdownHook(),實作了Tomcat的安全關閉,

在這里,我們可以了解一下鉤子方法的觸發時機:
1.程式正常退出
2.使用System.exit()
3.終端使用Ctrl+C觸發的中斷
4.系統關閉
5.OutOfMemory宕機
6.使用Kill pid命令干掉行程(但是在使用kill -9 pid時,是不會被呼叫的)

3.4.1.2 開啟8005埠

我們一路追蹤,org.apache.catalina.startup.Catalina#await->org.apache.catalina.core.StandardServer#await,發現這里開啟一個8005埠的socket服務,這是做什么用的?

不知道大家是否有發現,針對于腳本啟動tomcat和服務類啟動tomcat,他們是怎么關閉tomcat的?

1)針對腳本啟動的tomcat,有一個shutdown.bat或者是shutdown.sh檔案,這個檔案可以幫組我們關閉tomcat服務,

2)針對Windows下的服務類Tomcat,服務界面有一個stop的按鈕,可以關閉tomcat,

而這兩種關閉的方式,其實就是發送一個8005的socket請求,服務端接受到這個請求后,安全的將Tomcat關閉,

3.4.2 Server組件啟動(1.3.2:start)

這里仍然遵循上面的套路,通過呼叫父類的start()方法,經過一系列的狀態設定之后,呼叫真正的實作方法startInternal,

protected void startInternal() throws LifecycleException {
	fireLifecycleEvent(CONFIGURE_START_EVENT, null);
	setState(LifecycleState.STARTING);
	globalNamingResources.start();
	// Start our defined Services
	synchronized (servicesLock) {
		for (Service service : services) {
			service.start();
		}
	}
}

這里很簡單,主要遍歷啟動service,

3.4.3 Service組件啟動(1.3.3:start)

在這里,和service啟動一樣,主要做了三件事:

 protected void startInternal() throws LifecycleException {
	engine.start();
	executor.start();
	connector.start();
 }

3.4.4 Engine組件啟動(1.3.4:start)

org.apache.catalina.core.StandardEngine#startInternal->org.apache.catalina.core.ContainerBase#startInternal

在這里,主要呼叫threadStart()方法,

protected void threadStart() {
	if (thread != null)
		return;
	if (backgroundProcessorDelay <= 0)
		return;
	threadDone = false;
	String threadName = "ContainerBackgroundProcessor[" + toString() + "]";
	thread = new Thread(new ContainerBackgroundProcessor(), threadName);
	thread.setDaemon(true);
	thread.start();
}

在這里,我們主要關注ContainerBackgroundProcessor這個方法,tomat應用的動態部署就是通過這個類實作了,而這個類的官方說明如下:

/**
 * Private runnable class to invoke the backgroundProcess method
 * of this container and its children after a fixed delay.
 * 固定的可運行類,用于在固定延遲后呼叫此容器及其子級的backgroundProcess方法,
 */

以后也許會針對tomat的動態部署做一個專題,這里限于篇幅就不說了,

3.4.5 Executor組件啟動(1.3.5:start)

我們快速來到org.apache.catalina.core.StandardThreadExecutor#startInternal,

@Override
protected void startInternal() throws LifecycleException {

	taskqueue = new TaskQueue(maxQueueSize);
	TaskThreadFactory tf = new TaskThreadFactory(namePrefix,daemon,getThreadPriority());
	executor = new ThreadPoolExecutor(getMinSpareThreads(), getMaxThreads(), maxIdleTime, TimeUnit.MILLISECONDS,taskqueue, tf);
	executor.setThreadRenewalDelay(threadRenewalDelay);
	//預啟動最小的核心空閑執行緒
	if (prestartminSpareThreads) {
		executor.prestartAllCoreThreads();
	}
	taskqueue.setParent(executor);

	setState(LifecycleState.STARTING);
}

在這里,我們看到,還是以之前那種方式創建了執行緒池,這里也沒啥說的,

3.4.6 Connector組件啟動(1.3.6:start)

我們快速來到org.apache.catalina.connector.Connector#startInternal,

@Override
protected void startInternal() throws LifecycleException {

	// Validate settings before starting
	if (getPort() < 0) {
		throw new LifecycleException(sm.getString(
				"coyoteConnector.invalidPort", Integer.valueOf(getPort())));
	}
	setState(LifecycleState.STARTING);
	try {
		protocolHandler.start();
	} catch (Exception e) {
		throw new LifecycleException(
				sm.getString("coyoteConnector.protocolHandlerStartFailed"), e);
	}
}

是不是感覺很神奇?原來大神都是這么寫代碼?看似一段轟轟烈烈的代碼,其實有用的就是那么一兩句,然而體現代碼功底的確實那些看似“沒用的代碼”,這些“沒用的代碼”才讓整個系統具有更強的健壯性,一言不合就拋例外,言歸正傳,這里其實就是實作了protocolHandler.start();啟動協議處理器,

3.4.7 ProtocolHandler組件啟動(1.3.7:start)

我們依舊快速定位到org.apache.coyote.AbstractProtocol#start方法,這里有一句重要的代碼endpoint.start();,我們繼續往下跟蹤:

org.apache.tomcat.util.net.AbstractEndpoint#start->org.apache.tomcat.util.net.NioEndpoint#startInternal

public void startInternal() throws Exception {
	if (!running) {
		running = true;
		paused = false;
		processorCache = new SynchronizedStack<>(SynchronizedStack.DEFAULT_SIZE,
				socketProperties.getProcessorCache());
		eventCache = new SynchronizedStack<>(SynchronizedStack.DEFAULT_SIZE,
						socketProperties.getEventCache());
		nioChannels = new SynchronizedStack<>(SynchronizedStack.DEFAULT_SIZE,
				socketProperties.getBufferPool());
		// Create worker collection
		if ( getExecutor() == null ) {
			createExecutor();
		}
		//10000個
		initializeConnectionLatch();
		// Start poller threads
		//啟動poller執行緒,根據
		pollers = new Poller[getPollerThreadCount()];
		for (int i=0; i<pollers.length; i++) {
			pollers[i] = new Poller();
			Thread pollerThread = new Thread(pollers[i], getName() + "-ClientPoller-"+i);
			pollerThread.setPriority(threadPriority);
			pollerThread.setDaemon(true);
			pollerThread.start();
		}
		startAcceptorThreads();
	}
}

還記得我在上面說過NIOEndPoint和NIO2Endpoint的區別嗎?我們仔細看一下這一段代碼,先后啟動了兩型別執行緒,pollerThread和AcceptorThread,系統默認Acceptor有一個執行緒,pollerThread有兩個執行緒,

現在,我們啟動tomat程式,并啟動jconsole觀察一下執行緒,

到此為止,Tomcat的啟動流程算是告一段落了,然而,Tomcat的原始碼并沒有結束,以后會以專題形式針對特殊處理邏輯做一分享,

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/183704.html

標籤:java

上一篇:天府之國成都!2年Java后端小伙三面招銀網路輕松拿下offer

下一篇:Java雜談---Spring篇(1)

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Rust中的智能指標:Box<T> Rc<T> Arc<T> Cell<T> RefCell<T> Weak

    Rust中的智能指標是什么 智能指標(smart pointers)是一類資料結構,是擁有資料所有權和額外功能的指標。是指標的進一步發展 指標(pointer)是一個包含記憶體地址的變數的通用概念。這個地址參考,或 ” 指向”(points at)一些其 他資料 。參考以 & 符號為標志并借用了他們所 ......

    uj5u.com 2023-04-20 07:24:10 more
  • Java的值傳遞和參考傳遞

    值傳遞不會改變本身,參考傳遞(如果傳遞的值需要實體化到堆里)如果發生修改了會改變本身。 1.基本資料型別都是值傳遞 package com.example.basic; public class Test { public static void main(String[] args) { int ......

    uj5u.com 2023-04-20 07:24:04 more
  • [2]SpinalHDL教程——Scala簡單入門

    第一個 Scala 程式 shell里面輸入 $ scala scala> 1 + 1 res0: Int = 2 scala> println("Hello World!") Hello World! 檔案形式 object HelloWorld { /* 這是我的第一個 Scala 程式 * 以 ......

    uj5u.com 2023-04-20 07:23:58 more
  • 理解函式指標和回呼函式

    理解 函式指標 指向函式的指標。比如: 理解函式指標的偽代碼 void (*p)(int type, char *data); // 定義一個函式指標p void func(int type, char *data); // 宣告一個函式func p = func; // 將指標p指向函式func ......

    uj5u.com 2023-04-20 07:23:52 more
  • Django筆記二十五之資料庫函式之日期函式

    本文首發于公眾號:Hunter后端 原文鏈接:Django筆記二十五之資料庫函式之日期函式 日期函式主要介紹兩個大類,Extract() 和 Trunc() Extract() 函式作用是提取日期,比如我們可以提取一個日期欄位的年份,月份,日等資料 Trunc() 的作用則是截取,比如 2022-0 ......

    uj5u.com 2023-04-20 07:23:45 more
  • 一天吃透JVM面試八股文

    什么是JVM? JVM,全稱Java Virtual Machine(Java虛擬機),是通過在實際的計算機上仿真模擬各種計算機功能來實作的。由一套位元組碼指令集、一組暫存器、一個堆疊、一個垃圾回收堆和一個存盤方法域等組成。JVM屏蔽了與作業系統平臺相關的資訊,使得Java程式只需要生成在Java虛擬機 ......

    uj5u.com 2023-04-20 07:23:31 more
  • 使用Java接入小程式訂閱訊息!

    更新完微信服務號的模板訊息之后,我又趕緊把微信小程式的訂閱訊息給實作了!之前我一直以為微信小程式也是要企業才能申請,沒想到小程式個人就能申請。 訊息推送平臺🔥推送下發【郵件】【短信】【微信服務號】【微信小程式】【企業微信】【釘釘】等訊息型別。 https://gitee.com/zhongfuch ......

    uj5u.com 2023-04-20 07:22:59 more
  • java -- 緩沖流、轉換流、序列化流

    緩沖流 緩沖流, 也叫高效流, 按照資料型別分類: 位元組緩沖流:BufferedInputStream,BufferedOutputStream 字符緩沖流:BufferedReader,BufferedWriter 緩沖流的基本原理,是在創建流物件時,會創建一個內置的默認大小的緩沖區陣列,通過緩沖 ......

    uj5u.com 2023-04-20 07:22:49 more
  • Java-SpringBoot-Range請求頭設定實作視頻分段傳輸

    老實說,人太懶了,現在基本都不喜歡寫筆記了,但是網上有關Range請求頭的文章都太水了 下面是抄的一段StackOverflow的代碼...自己大修改過的,寫的注釋挺全的,應該直接看得懂,就不解釋了 寫的不好...只是希望能給視頻網站開發的新手一點點幫助吧. 業務場景:視頻分段傳輸、視頻多段傳輸(理 ......

    uj5u.com 2023-04-20 07:22:42 more
  • Windows 10開發教程_編程入門自學教程_菜鳥教程-免費教程分享

    教程簡介 Windows 10開發入門教程 - 從簡單的步驟了解Windows 10開發,從基本到高級概念,包括簡介,UWP,第一個應用程式,商店,XAML控制元件,資料系結,XAML性能,自適應設計,自適應UI,自適應代碼,檔案管理,SQLite資料庫,應用程式到應用程式通信,應用程式本地化,應用程式 ......

    uj5u.com 2023-04-20 07:22:35 more