主頁 >  其他 > Tomcat原始碼分析--啟動流程

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

2020-10-22 01:38:04 其他

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/qita/184848.html

標籤:其他

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

下一篇:攤牌了,我要手寫一個RPC

標籤雲
其他(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)

熱門瀏覽
  • 網閘典型架構簡述

    網閘架構一般分為兩種:三主機的三系統架構網閘和雙主機的2+1架構網閘。 三主機架構分別為內端機、外端機和仲裁機。三機無論從軟體和硬體上均各自獨立。首先從硬體上來看,三機都用各自獨立的主板、記憶體及存盤設備。從軟體上來看,三機有各自獨立的作業系統。這樣能達到完全的三機獨立。對于“2+1”系統,“2”分為 ......

    uj5u.com 2020-09-10 02:00:44 more
  • 如何從xshell上傳檔案到centos linux虛擬機里

    如何從xshell上傳檔案到centos linux虛擬機里及:虛擬機CentOs下執行 yum -y install lrzsz命令,出現錯誤:鏡像無法找到軟體包 前言 一、安裝lrzsz步驟 二、上傳檔案 三、遇到的問題及解決方案 總結 前言 提示:其實很簡單,往虛擬機上安裝一個上傳檔案的工具 ......

    uj5u.com 2020-09-10 02:00:47 more
  • 一、SQLMAP入門

    一、SQLMAP入門 1、判斷是否存在注入 sqlmap.py -u 網址/id=1 id=1不可缺少。當注入點后面的引數大于兩個時。需要加雙引號, sqlmap.py -u "網址/id=1&uid=1" 2、判斷文本中的請求是否存在注入 從文本中加載http請求,SQLMAP可以從一個文本檔案中 ......

    uj5u.com 2020-09-10 02:00:50 more
  • Metasploit 簡單使用教程

    metasploit 簡單使用教程 浩先生, 2020-08-28 16:18:25 分類專欄: kail 網路安全 linux 文章標簽: linux資訊安全 編輯 著作權 metasploit 使用教程 前言 一、Metasploit是什么? 二、準備作業 三、具體步驟 前言 Msfconsole ......

    uj5u.com 2020-09-10 02:00:53 more
  • 游戲逆向之驅動層與用戶層通訊

    驅動層代碼: #pragma once #include <ntifs.h> #define add_code CTL_CODE(FILE_DEVICE_UNKNOWN,0x800,METHOD_BUFFERED,FILE_ANY_ACCESS) /* 更多游戲逆向視頻www.yxfzedu.com ......

    uj5u.com 2020-09-10 02:00:56 more
  • 北斗電力時鐘(北斗授時服務器)讓網路資料更精準

    北斗電力時鐘(北斗授時服務器)讓網路資料更精準 北斗電力時鐘(北斗授時服務器)讓網路資料更精準 京準電子科技官微——ahjzsz 近幾年,資訊技術的得了快速發展,互聯網在逐漸普及,其在人們生活和生產中都得到了廣泛應用,并且取得了不錯的應用效果。計算機網路資訊在電力系統中的應用,一方面使電力系統的運行 ......

    uj5u.com 2020-09-10 02:01:03 more
  • 【CTF】CTFHub 技能樹 彩蛋 writeup

    ?碎碎念 CTFHub:https://www.ctfhub.com/ 筆者入門CTF時時剛開始刷的是bugku的舊平臺,后來才有了CTFHub。 感覺不論是網頁UI設計,還是題目質量,賽事跟蹤,工具軟體都做得很不錯。 而且因為獨到的金幣制度的確讓人有一種想去刷題賺金幣的感覺。 個人還是非常喜歡這個 ......

    uj5u.com 2020-09-10 02:04:05 more
  • 02windows基礎操作

    我學到了一下幾點 Windows系統目錄結構與滲透的作用 常見Windows的服務詳解 Windows埠詳解 常用的Windows注冊表詳解 hacker DOS命令詳解(net user / type /md /rd/ dir /cd /net use copy、批處理 等) 利用dos命令制作 ......

    uj5u.com 2020-09-10 02:04:18 more
  • 03.Linux基礎操作

    我學到了以下幾點 01Linux系統介紹02系統安裝,密碼啊破解03Linux常用命令04LAMP 01LINUX windows: win03 8 12 16 19 配置不繁瑣 Linux:redhat,centos(紅帽社區版),Ubuntu server,suse unix:金融機構,證券,銀 ......

    uj5u.com 2020-09-10 02:04:30 more
  • 05HTML

    01HTML介紹 02頭部標簽講解03基礎標簽講解04表單標簽講解 HTML前段語言 js1.了解代碼2.根據代碼 懂得挖掘漏洞 (POST注入/XSS漏洞上傳)3.黑帽seo 白帽seo 客戶網站被黑帽植入劫持代碼如何處理4.熟悉html表單 <html><head><title>TDK標題,描述 ......

    uj5u.com 2020-09-10 02:04:36 more
最新发布
  • 2023年最新微信小程式抓包教程

    01 開門見山 隔一個月發一篇文章,不過分。 首先回顧一下《微信系結手機號資料庫被脫庫事件》,我也是第一時間得知了這個訊息,然后跟蹤了整件事情的經過。下面是這起事件的相關截圖以及近日流出的一萬條資料樣本: 個人認為這件事也沒什么,還不如關注一下之前45億快遞資料查詢渠道疑似在近日復活的訊息。 訊息是 ......

    uj5u.com 2023-04-20 08:48:24 more
  • web3 產品介紹:metamask 錢包 使用最多的瀏覽器插件錢包

    Metamask錢包是一種基于區塊鏈技術的數字貨幣錢包,它允許用戶在安全、便捷的環境下管理自己的加密資產。Metamask錢包是以太坊生態系統中最流行的錢包之一,它具有易于使用、安全性高和功能強大等優點。 本文將詳細介紹Metamask錢包的功能和使用方法。 一、 Metamask錢包的功能 數字資 ......

    uj5u.com 2023-04-20 08:47:46 more
  • vulnhub_Earth

    前言 靶機地址->>>vulnhub_Earth 攻擊機ip:192.168.20.121 靶機ip:192.168.20.122 參考文章 https://www.cnblogs.com/Jing-X/archive/2022/04/03/16097695.html https://www.cnb ......

    uj5u.com 2023-04-20 07:46:20 more
  • 從4k到42k,軟體測驗工程師的漲薪史,給我看哭了

    清明節一過,盲猜大家已經無心上班,在數著日子準備過五一,但一想到銀行卡里的余額……瞬間心情就不美麗了。最近,2023年高校畢業生就業調查顯示,本科畢業月平均起薪為5825元。調查一出,便有很多同學表示自己又被平均了。看著這一資料,不免讓人想到前不久中國青年報的一項調查:近六成大學生認為畢業10年內會 ......

    uj5u.com 2023-04-20 07:44:00 more
  • 最新版本 Stable Diffusion 開源 AI 繪畫工具之中文自動提詞篇

    🎈 標簽生成器 由于輸入正向提示詞 prompt 和反向提示詞 negative prompt 都是使用英文,所以對學習母語的我們非常不友好 使用網址:https://tinygeeker.github.io/p/ai-prompt-generator 這個網址是為了讓大家在使用 AI 繪畫的時候 ......

    uj5u.com 2023-04-20 07:43:36 more
  • 漫談前端自動化測驗演進之路及測驗工具分析

    隨著前端技術的不斷發展和應用程式的日益復雜,前端自動化測驗也在不斷演進。隨著 Web 應用程式變得越來越復雜,自動化測驗的需求也越來越高。如今,自動化測驗已經成為 Web 應用程式開發程序中不可或缺的一部分,它們可以幫助開發人員更快地發現和修復錯誤,提高應用程式的性能和可靠性。 ......

    uj5u.com 2023-04-20 07:43:16 more
  • CANN開發實踐:4個DVPP記憶體問題的典型案例解讀

    摘要:由于DVPP媒體資料處理功能對存放輸入、輸出資料的記憶體有更高的要求(例如,記憶體首地址128位元組對齊),因此需呼叫專用的記憶體申請介面,那么本期就分享幾個關于DVPP記憶體問題的典型案例,并給出原因分析及解決方法。 本文分享自華為云社區《FAQ_DVPP記憶體問題案例》,作者:昇騰CANN。 DVPP ......

    uj5u.com 2023-04-20 07:43:03 more
  • msf學習

    msf學習 以kali自帶的msf為例 一、msf核心模塊與功能 msf模塊都放在/usr/share/metasploit-framework/modules目錄下 1、auxiliary 輔助模塊,輔助滲透(埠掃描、登錄密碼爆破、漏洞驗證等) 2、encoders 編碼器模塊,主要包含各種編碼 ......

    uj5u.com 2023-04-20 07:42:59 more
  • Halcon軟體安裝與界面簡介

    1. 下載Halcon17版本到到本地 2. 雙擊安裝包后 3. 步驟如下 1.2 Halcon軟體安裝 界面分為四大塊 1. Halcon的五個助手 1) 影像采集助手:與相機連接,設定相機引數,采集影像 2) 標定助手:九點標定或是其它的標定,生成標定檔案及內參外參,可以將像素單位轉換為長度單位 ......

    uj5u.com 2023-04-20 07:42:17 more
  • 在MacOS下使用Unity3D開發游戲

    第一次發博客,先發一下我的游戲開發環境吧。 去年2月份買了一臺MacBookPro2021 M1pro(以下簡稱mbp),這一年來一直在用mbp開發游戲。我大致分享一下我的開發工具以及使用體驗。 1、Unity 官網鏈接: https://unity.cn/releases 我一般使用的Apple ......

    uj5u.com 2023-04-20 07:40:19 more