Tomcat深入解析與性能優(yōu)化
Java 2019 年生態(tài)圈使用報告
2020 Java 生態(tài)系統(tǒng)報告
從軟件架構(gòu)的發(fā)展角度來看,軟件架構(gòu)大致經(jīng)歷了如下幾個階段:
從 Java Web 角度來說,架構(gòu)大致經(jīng)歷了:
從當(dāng)前企業(yè)使用的架構(gòu)角度來說,使用SSM架構(gòu)項目比較多,SSH基本被淘汰(大部分是老項目維護(hù)),很大一部分企業(yè)轉(zhuǎn)向微服務(wù)架構(gòu)了。
基于Spring 生態(tài)來說,大部分中小型企業(yè)都基本使用SpringBoot,SpringBoot本身集成了 tomcat、jetty和undertwo 容器,那么我們?yōu)槭裁葱枰〞r間來研究tomcat呢?
- 當(dāng)前tomcat依然是主流java web容器,研究它符合java 技術(shù)生態(tài)發(fā)展;
- 在java web項目調(diào)優(yōu)中,如ssm項目中,在優(yōu)化項目時,jvm和tomcat同樣重要,都需要優(yōu)化;
- 盡管springboot內(nèi)置了tomcat容器,且配置了默認(rèn)的tomcat參數(shù),但當(dāng)默認(rèn)的tomcat參數(shù)滿足不了項目優(yōu)化要求時,就需要優(yōu)化人員手動進(jìn)行相關(guān)的參數(shù)優(yōu)化,因此研究tomcat非常必要;
- 熟悉tomcat架構(gòu),是后續(xù)進(jìn)行項目優(yōu)化的基礎(chǔ),也是必備條件。
Tomcat架構(gòu)說明
知識點:
- Tomcat目錄結(jié)構(gòu)
- Tomcat簡要架構(gòu)
- Tomcat各組件及關(guān)系
- Tomcat server.xml配置詳解
- Tomcat啟動參數(shù)說明(啟動腳本)
Tomcat 是一個基于JAVA的WEB容器,其實現(xiàn)了JAVA EE中的 Servlet 與 jsp 規(guī)范,與Nginx Apache 服務(wù)器不同在于一般用于動態(tài)請求處理。在架構(gòu)設(shè)計上采用面向組件的方式設(shè)計。即整體功能是通過組件的方式拼裝完成。另外每個組件都可以被替換以保證靈活性。
通過Tomcat官方可以看到,目前已經(jīng)更新到Tomcat 10了,但當(dāng)前大部分企業(yè)使用的Tomcat 為8或者9版本
Tomcat 目錄結(jié)構(gòu)
- bin:可執(zhí)行文件,.sh結(jié)尾的表示linux可執(zhí)行文件,.bat結(jié)尾的表示windows可執(zhí)行文件
- conf:配置文件
- lib:tomcat相關(guān)jar包
- temp:臨時文件
- webapps:存放項目
- work:工作目錄
bin目錄
bin目錄存放可執(zhí)行文件,簡要結(jié)束常用命令
這里主要解釋如下通用的命令,其他命令就不一一介紹
- catalina.sh 真正啟動Tomcat文件,可以在里面設(shè)置jvm參數(shù)
- startup.sh 程序項目命令文件
- version.sh 查看tomcat版本相關(guān)信息命令文件
- shutdown.sh 關(guān)閉程序命令
conf目錄
conf文件夾用來存放tomcat相關(guān)配置文件
1.catalina.policy
項目安全文件,用來防止欺騙代碼或JSP執(zhí)行帶有像System.exit(0)這樣的命令的可能影響容器的破壞性代碼. 只有當(dāng)Tomcat用-security命令行參數(shù)啟動時這個文件才會被使用,即啟動tomcat時, startup.sh -security 。
上圖中,tomcat容器下部署兩個項目,項目1和項目2。由于項目1中有代碼System.exit(0),當(dāng)訪問該代碼時,該代碼會導(dǎo)致整個tomcat停止,從而也導(dǎo)致項目2停止。
為了解決因項目1存在欺騙代碼或不安全代碼導(dǎo)致?lián)p害Tomcat容器,從而影響其他項目正常運行的問題,啟動tomcat容器時,加上-security參數(shù)就,即startup.sh -security,如此即使項目1中有代碼System.exit(0),也只會僅僅停止項目1,而不會影響Tomcat容器,然而起作用的配置文件就是catalina.policy文件。
2.catalina.properties
配置tomcat啟動相關(guān)信息文件
3.context.xml
監(jiān)視并加載資源文件,當(dāng)監(jiān)視的文件發(fā)生發(fā)生變化時,自動加載
4.jaspic-providers.xml 和 jaspic-providers.xsd
這兩個文件不常用
5.logging.properties
該文件為tomcat日志文件,包括配置tomcat輸出格式,日志級別等
6.server.xml
tomcat核心架構(gòu)主件文件,下面會詳細(xì)解析。
7.tomcat-users.xml和tomcat-users.xsd
tomcat用戶文件,如配置遠(yuǎn)程登陸賬號
tomcat-users.xsd 為tomcat-users.xml描述和約束文件
8.web.xml
tomcat全局配置文件。
lib目錄
lib文件夾主要用來存放tomcat依賴jar包,如下為 tomcat 的lib文件夾下的相關(guān)jar包。
每個jar包功能,這里就不講解了,這里主要分析ecj-4.13.jar,這個jar包起到將.java編譯成.class字節(jié)碼作用。
假設(shè)要編譯MyTest.java,那么jdk會執(zhí)行兩步:
- 第一步:將MyTest.java編譯成MyTest.class
javac MyTest.java
- 第二步:執(zhí)行MyTest.class
java MyTest.class
- 那么,使用ecj-4.13.jar如執(zhí)行MyTest.java呢?
java -jar ecj-4.13.jar MyTest.java
logs目錄
該文件夾表示tomcat日志文件,大致包括如下六類文件:
temp目錄
temp目錄用戶存放tomcat在運行過程中產(chǎn)生的臨時文件。(清空不會對tomcat運行帶來影響)。
webapps目錄
webapps目錄用來存放應(yīng)用程序,當(dāng)tomcat啟動時會去加載webapps目錄下的應(yīng)用程序??梢砸晕募A、war包、jar包的形式發(fā)布應(yīng)用。
當(dāng)然,你也可以把應(yīng)用程序放置在磁盤的任意位置,在配置文件中映射好就行。
work目錄
work目錄用來存放tomcat在運行時的編譯后文件,例如JSP編譯后的文件。
清空work目錄,然后重啟tomcat,可以達(dá)到清除緩存的作用。
Tomcat 簡要架構(gòu)
Tomcat 各組件及關(guān)系
- Server 和 Service
- Connector 連接器
- HTTP 1.1
- SSL https
- AJP( Apache JServ Protocol) apache 私有協(xié)議,用于apache 反向代理Tomcat
- Container
- Engine 引擎 catalina
- Host 虛擬機(jī) 基于域名 分發(fā)請求
- Context 隔離各個WEB應(yīng)用 每個Context的 ClassLoader都是獨立
- Component
- Manager (管理器)
- logger (日志管理)
- loader (載入器)
- pipeline (管道)
- valve (管道中的閥)
Tomcat server.xml 配置詳解
Server 的基本基本配置:
- <Server>
- <Listener /><!-- 監(jiān)聽器 -->
- <GlobaNamingResources> <!-- 全局資源 -->
- </GlobaNamingResources
- <Service> <!-- 服務(wù) 用于 綁定 連接器與 Engine -->
- <Connector 8080/> <!-- 連接器-->
- <Connector 8010 /> <!-- 連接器-->
- <Connector 8030/> <!-- 連接器-->
- <Engine> <!-- 執(zhí)行引擎-->
- <Logger />
- <Realm />
- <host "www.test.com" appBase=""> <!-- 虛擬主機(jī)-->
- <Logger /> <!-- 日志配置-->
- <Context "/applction" path=""/> <!-- 上下文配置-->
- </host>
- </Engine>
- </Service>
- </Server>
server
root元素:server 的頂級配置
主要屬性:
port:執(zhí)行關(guān)閉命令的端口號
shutdown:關(guān)閉命令
- 演示shutdown的用法
- #基于telent 執(zhí)行SHUTDOWN 命令即可關(guān)閉
- telent 127.0.0.1 8005
- SHUTDOWN
service
服務(wù):將多個connector 與一個Engine組合成一個服務(wù),可以配置多個服務(wù)。
Connector
連接器:用于接收 指定協(xié)議下的連接 并指定給唯一的Engine 進(jìn)行處理。
主要屬性:
- protocol 監(jiān)聽的協(xié)議,默認(rèn)是http/1.1
- port 指定服務(wù)器端要創(chuàng)建的端口號
- minThread 服務(wù)器啟動時創(chuàng)建的處理請求的線程數(shù)
- maxThread 最大可以創(chuàng)建的處理請求的線程數(shù)
- enableLookups 如果為true,則可以通過調(diào)用request.getRemoteHost()進(jìn)行DNS查詢來得到遠(yuǎn)程客戶端的實際主機(jī)名,若為false則不進(jìn)行DNS查詢,而是返回其ip地址
- redirectPort 指定服務(wù)器正在處理http請求時收到了一個SSL傳輸請求后重定向的端口號
- acceptCount 指定當(dāng)所有可以使用的處理請求的線程數(shù)都被使用時,可以放到處理隊列中的請求數(shù),超過這個數(shù)的請求將不予處理,默認(rèn)100;
- address 綁定客戶端特定地址,127.0.0.1
- bufferSize 每個請求的緩沖區(qū)大小 bufferSize * maxThreads
- compression 是否啟用文檔壓縮
- compressionMinSize 文檔壓縮的最小大小
- compressableMimeTypes text/html,text/xml,text/plain
- connectionTimeout 客戶端發(fā)起鏈接到服務(wù)端接收為止,指定超時的時間數(shù)(以毫秒為單位)
- connectionUploadTimeout upload情況下連接超時時間
- disableUploadTimeout 如果為true則使用 connectionTimeout
- keepAliveTimeout 當(dāng)長鏈接閑置 指定時間主動關(guān)閉 鏈接 ,前提是客戶端請求頭 帶上這個 head"connection" " keep-alive"
- maxKeepAliveRequests 最大的 長連接數(shù) 默認(rèn)最大100
- maxSpareThreads BIO 模式下 最多線閑置線程數(shù)
- minSpareThreads BIO 模式下 最小線閑置線程數(shù)
- SSLEnabled 是否開啟 sll 驗證,在Https 訪問時需要開啟。
- 演示配置多個Connector
- <Connector port="8860" protocol="org.apache.coyote.http11.Http11NioProtocol"
- connectionTimeout="20000"
- redirectPort="8862"
- URIEncoding="UTF-8"
- useBodyEncodingForURI="true"
- compression="on" compressionMinSize="2048"
- compressableMimeType="text/html,text/xml,text/plain,text/javascript,text/css,application/x-json,application/json,application/x-javascript"
- maxThreads="1024" minSpareThreads="200"
- acceptCount="800"
- enableLookups="false"
- />
Engine
引擎:用于處理連接的執(zhí)行器,默認(rèn)的引擎是catalina。一個service 中只能配置一個Engine。
主要屬性:name 引擎名稱 defaultHost 默認(rèn)host
Host
虛擬機(jī):基于域名匹配至指定虛擬機(jī)。類似于nginx 當(dāng)中的server,默認(rèn)的虛擬機(jī)是localhost.
演示配置多個Host
- <Host name="www.test.com" appBase="/usr/www/test"
- unpackWARs="true" autoDeploy="true">
- <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs" prefix="www.luban.com.access_log" suffix=".txt"
- pattern="%h %l %u %t "%r" %s %b" />
- </Host>
Context
應(yīng)用上下文:一個host 下可以配置多個Context ,每個Context 都有其獨立的classPath。相互隔離,以免造成ClassPath 沖突。
- 演示配置多個Context
- <Context docBase="hello" path="/h" reloadable="true"/>
Valve
閥門:可以理解成request 的過濾器,具體配置要基于具體的Valve 接口的子類。以下即為一個訪問日志的Valve.
- <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
- prefix="www.luban.com.access_log" suffix=".txt"
- pattern="%h %l %u %t "%r" %s %b" />
Tomcat啟動參數(shù)說明
我們平時啟動Tomcat過程是怎么樣的?
- 復(fù)制WAR包至Tomcat webapp 目錄。
- 執(zhí)行starut.bat 腳本啟動。
- 啟動過程中war 包會被自動解壓裝載。
但是我們在Eclipse 或idea 中啟動WEB項目的時候 也是把War包復(fù)雜至webapps 目錄解壓嗎?顯然不是,其真正做法是在Tomcat程序文件之外創(chuàng)建了一個部署目錄,在一般生產(chǎn)環(huán)境中也是這么做的 即:Tomcat 程序目錄和部署目錄分開 。
我們只需要在啟動時指定CATALINA_HOME 與 CATALINA_BASE 參數(shù)即可實現(xiàn)。
可以編寫一個腳本 來實現(xiàn)自定義配置:
更新 啟動 腳本:
- #!/bin/bash -e
- export now_time=$(date +%Y-%m-%d_%H-%M-%S)echo "deploy time:$now_time"app=$1version=$2mkdir -p war/#從svn下載程序至 war目錄war=war/${app}_${version}.warecho "$war"svn export svn://192.168.0.253/release/${app}_${version}.war $wardeploy_war() {#解壓版本至當(dāng)前目錄target_dir=war/${app}_${version}_${now_time}unzip -q $war -d $target_dirrm -f appwarln -sf $target_dir appwartarget_ln=`pwd`/appwarecho '<?xml version="1.0" encoding="UTF-8" ?>
- <Context docBase="'$target_ln'" allowLinking="false">
- </Context>' > conf/Catalina/localhost/ROOT.xml
- #重啟Tomcat服務(wù)./tomcat.sh restart}deploy_war```
自動部署腳本:
- #!/bin/bash -e
- export now_time=$(date +%Y-%m-%d_%H-%M-%S)echo "deploy time:$now_time"app=$1version=$2mkdir -p war/#從svn下載程序至 war目錄war=war/${app}_${version}.warecho "$war"svn export svn://192.168.0.253/release/${app}_${version}.war $wardeploy_war() {#解壓版本至當(dāng)前目錄target_dir=war/${app}_${version}_${now_time}unzip -q $war -d $target_dirrm -f appwarln -sf $target_dir appwartarget_ln=`pwd`/appwarecho '<?xml version="1.0" encoding="UTF-8" ?>
- <Context docBase="'$target_ln'" allowLinking="false">
- </Context>' > conf/Catalina/localhost/ROOT.xml
- #重啟Tomcat服務(wù)./tomcat.sh restart}deploy_war```
Tomcat 網(wǎng)絡(luò)通信模型剖析
Tomcat 支持四種線程模型介紹
什么是IO?
IO是指為數(shù)據(jù)傳輸所提供的輸入輸出流,其輸入輸出對象可以是:文件、網(wǎng)絡(luò)服務(wù)、內(nèi)存等。
什么是IO模型?
提問:
假設(shè)應(yīng)用在從硬盤中讀取一個大文件過程中,此時CPU會與硬盤一樣處于高負(fù)荷狀態(tài)么?
演示:
- 演示觀察大文件的讀寫過程當(dāng)中CPU 有沒有發(fā)生大波動。
演示結(jié)果:CPU 沒有太高的增長
通常情況下IO操作是比較耗時的,所以為了高效的使用硬件,應(yīng)用程序可以用一個專門線程進(jìn)行IO操作,而另外一個線程則利用CPU的空閑去做其它計算。這種為提高應(yīng)用執(zhí)行效率而采用的IO操作方法即為IO模型。
各IO模型簡要說明
BIO
阻塞式IO,即Tomcat使用傳統(tǒng)的java.io進(jìn)行操作。該模式下每個請求都會創(chuàng)建一個線程,對性能開銷大,不適合高并發(fā)場景。優(yōu)點是穩(wěn)定,適合連接數(shù)目小且固定架構(gòu)。
NIO
非阻塞式IO,jdk1.4 之后實現(xiàn)的新IO。該模式基于多路復(fù)用選擇器監(jiān)測連接狀態(tài)在通知線程處理,從而達(dá)到非阻塞的目的。比傳統(tǒng)BIO能更好的支持并發(fā)性能。Tomcat 8.0之后默認(rèn)采用該模式
APR
全稱是 Apache Portable Runtime/Apache可移植運行庫),是Apache HTTP服務(wù)器的支持庫。可以簡單地理解為,Tomcat將以JNI的形式調(diào)用Apache HTTP服務(wù)器的核心動態(tài)鏈接庫來處理文件讀取或網(wǎng)絡(luò)傳輸操作。使用需要編譯安裝APR 庫
AIO
異步非阻塞式IO,jdk1.7后之支持 。與nio不同在于不需要多路復(fù)用選擇器,而是請求處理線程執(zhí)行完成進(jìn)行回調(diào)調(diào)制,已繼續(xù)執(zhí)行后續(xù)操作。Tomcat 8之后支持。
使用指定IO模型的配置方式:
配置 server.xml 文件當(dāng)中的 <Connector protocol="HTTP/1.1"> 修改即可。
默認(rèn)配置 8.0 protocol=“HTTP/1.1” 8.0 之前是 BIO, 8.0 之后是 NIO
BIO
protocol=“org.apache.coyote.http11.Http11Protocol”
NIO
protocol=“org.apache.coyote.http11.Http11NioProtocol”
AIO
protocol=“org.apache.coyote.http11.Http11Nio2Protocol”
APR
protocol=“org.apache.coyote.http11.Http11AprProtocol”
Tomcat BIO、NIO實現(xiàn)過程源碼解析
BIO 與NIO區(qū)別
分別演示在高并發(fā)場景下BIO與NIO的線程數(shù)的變化?
BIO 配置
- <Connector port="8080" protocol="org.apache.coyote.http11.Http11Protocol"
- connectionTimeout="20000"
- redirectPort="8443"
- compression="on" compressionMinSize="1024"
- compressableMimeType="text/html,text/xml,text/plain,text/javascript,text/css,application/x-json,application/json,application/x-javascript"
- maxThreads="500" minSpareThreads="1"/>
NIO配置
- <Connector port="8080" protocol="org.apache.coyote.http11.Http11NioProtocol"
- connectionTimeout="20000"
- redirectPort="8443"
- compression="on" compressionMinSize="1024"
- compressableMimeType="text/html,text/xml,text/plain,text/javascript,text/css,application/x-json,application/json,application/x-javascript"
- maxThreads="500" minSpareThreads="1"/>
演示數(shù)據(jù):
生成環(huán)境重要因素:
- 網(wǎng)絡(luò)
- 程序執(zhí)行業(yè)務(wù)用時
源代碼地址:https://github.com/org-hejianhui/bit-bigdata-transmission
BIO 線程模型
BIO 源碼
線程組:
Accept 線程組 acceptorThreadCount 默認(rèn)1個
exec 線程組 maxThread
JIoEndpoint
Acceptor extends Runnable
SocketProcessor extends Runnable
NIO 線程模型
NIO 線程模型
Accept 線程組 默認(rèn)兩個輪詢器
Poller Selector PollerEvent輪詢線程狀態(tài)
SocketProcessor
BIO
線程數(shù)量 會受到 客戶端阻塞、網(wǎng)絡(luò)延遲、業(yè)務(wù)處理慢===>線程數(shù)會更多。
NIO
線程數(shù)量 會受到業(yè)務(wù)處理慢===>線程數(shù)會更多。
Tomcat connector 并發(fā)參數(shù)解讀
Tomcat 類加載機(jī)制源碼解析
類加載的本質(zhì)
是用來加載 Class 的。它負(fù)責(zé)將 Class 的字節(jié)碼形式轉(zhuǎn)換成內(nèi)存形式的 Class 對象。字節(jié)碼可以來自于磁盤文件 .class,也可以是 jar 包里的 .class,也可以來自遠(yuǎn)程服務(wù)器提供的字節(jié)流,字節(jié)碼的本質(zhì)就是一個字節(jié)數(shù)組 []byte,它有特定的復(fù)雜的內(nèi)部格式。
JVM 運行實例中會存在多個 ClassLoader,不同的 ClassLoader 會從不同的地方加載字節(jié)碼文件。它可以從不同的文件目錄加載,也可以從不同的 jar 文件中加載,也可以從網(wǎng)絡(luò)上不同的靜態(tài)文件服務(wù)器來下載字節(jié)碼再加載。
jvm里ClassLoader的層次結(jié)構(gòu)
類加載器層次結(jié)構(gòu)
BootstrapClassLoader(啟動類加載器)
稱為啟動類加載器,是Java類加載層次中最頂層的類加載器,負(fù)責(zé)加載JDK中的核心類庫,如:rt.jar、resources.jar、charsets.jar等,可通過如下程序獲得該類加載器從哪些地方加載了相關(guān)的jar或class文件:
- URL[] urLs = sun.misc.Launcher.getBootstrapClassPath().getURLs();
- for (URL url : urLs) {
- System.out.println(url.toExternalForm());
- }
程序執(zhí)行結(jié)果如下:
- file:/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/jre/lib/resources.jar
- file:/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/jre/lib/rt.jar
- file:/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/jre/lib/sunrsasign.jar
- file:/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/jre/lib/jsse.jar
- file:/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/jre/lib/jce.jar
- file:/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/jre/lib/charsets.jar
- file:/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/jre/lib/jfr.jar
- file:/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/jre/classes
從rt.jar中選擇String類,看一下String類的類加載器是什么
- ClassLoader classLoader = String.class.getClassLoader();
- System.out.println(classLoader);
執(zhí)行結(jié)果如下:
- null
可知由于BootstrapClassLoader對Java不可見,所以返回了null,我們也可以通過某一個類的加載器是否為null來作為判斷該類是不是使用BootstrapClassLoader進(jìn)行加載的依據(jù)。
ExtensionClassLoader
ExtClassLoader稱為擴(kuò)展類加載器,主要負(fù)責(zé)加載Java的擴(kuò)展類庫,默認(rèn)加載JAVA_HOME/jre/lib/ext/目錄下的所有jar包或者由java.ext.dirs系統(tǒng)屬性指定的jar包.放入這個目錄下的jar包對AppClassLoader加載器都是可見的(因為ExtClassLoader是AppClassLoader的父加載器,并且Java類加載器采用了委托機(jī)制)。
ExtClassLoader的類掃描路徑通過執(zhí)行下面代碼來看一下:
- String extDirs = System.getProperty("java.ext.dirs");
- for (String path : extDirs.split(";")) {
- System.out.println(path);}
執(zhí)行結(jié)果如下(Mac系統(tǒng)):
- /Users/hjh/Library/Java/Extensions:/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/jre/lib/ext:/Library/Java/Extensions:/Network/Library/Java/Extensions:/System/Library/Java/Extensions:/usr/lib/java
jre/lib/ext路徑下內(nèi)容為:
從上面的路徑中隨意選擇一個類,來看看它的類加載器是什么:
- sun.misc.Launcher$ExtClassLoader@4439f31e
- null
從上面的程序運行結(jié)果可知ExtClassLoader的父加載器為null,之前說過BootstrapClassLoader對Java不可見,所以返回了null。ExtClassLoader的父加載器返回的是null,那是否說明ExtClassLoader的父加載器是BootstrapClassLoader?
- Bootstrap ClassLoader是由C/C++編寫的,它本身是虛擬機(jī)的一部分,所以它并不是一個JAVA類,也就是無法在java代碼中獲取它的引用,JVM啟動時通過Bootstrap類加載器加載rt.jar等核心jar包中的class文件,之前的int.class,String.class都是由它加載。然后呢,我們前面已經(jīng)分析了,JVM初始化sun.misc.Launcher并創(chuàng)建Extension ClassLoader和AppClassLoader實例。并將ExtClassLoader設(shè)置為AppClassLoader的父加載器。Bootstrap沒有父加載器,但是它卻可以作用一個ClassLoader的父加載器。比如ExtClassLoader。這也可以解釋之前通過ExtClassLoader的getParent方法獲取為Null的現(xiàn)象
AppClassLoader
才是直接面向我們用戶的加載器,它會加載 Classpath 環(huán)境變量里定義的路徑中的 jar 包和目錄。我們自己編寫的代碼以及使用的第三方 jar 包通常都是由它來加載的。
加載System.getProperty("java.class.path")所指定的路徑或jar。在使用Java運行程序時,也可以加上-cp來覆蓋原有的Classpath設(shè)置,例如: java -cp ./lavasoft/classes HelloWorld
- public class AppClassLoaderTest {
- public static void main(String[] args) {
- System.out.println(ClassLoader.getSystemClassLoader());
- }}
輸出結(jié)果如下:
- sun.misc.Launcher$AppClassLoader@18b4aac2
以上結(jié)論說明調(diào)用ClassLoader.getSystemClassLoader()可以獲得AppClassLoader類加載器。
- protected ClassLoader() {
- this(checkCreateClassLoader(), getSystemClassLoader());
- }
通過查看ClassLoader的源碼發(fā)現(xiàn)并且在沒有特定說明的情況下,用戶自定義的任何類加載器都將該類加載器作為自定義類加載器的父加載器。
通過執(zhí)行上面的代碼即可獲得classpath的加載路徑。
在上面的main函數(shù)的類的加載就是使用AppClassLoader加載器進(jìn)行加載的,可以通過執(zhí)行下面的代碼得出這個結(jié)論
- public class AppClassLoaderTest {
- public static void main(String[] args) {
- ClassLoader classLoader = Test.class.getClassLoader();
- System.out.println(classLoader); System.out.println(classLoader.getParent()); } private static class Test {
- }}
執(zhí)行結(jié)果如下:
- sun.misc.Launcher$AppClassLoader@18b4aac2
- sun.misc.Launcher$ExtClassLoader@2d209079
從上面的運行結(jié)果可以得知AppClassLoader的父加載器是ExtClassLoader
Tomcat的 類加載順序
在Tomcat中,默認(rèn)的行為是先嘗試在Bootstrap和Extension中進(jìn)行類型加載,如果加載不到則在Webapp ClassLoader中進(jìn)行加載,如果還是找不到則在Common中進(jìn)行查找。
NoClassDefFoundError
NoClassDefFoundError是在開發(fā)JavaEE程序中常見的一種問題。該問題會隨著你所使用的JavaEE中間件環(huán)境的復(fù)雜度以及應(yīng)用本身的體量變得更加復(fù)雜,尤其是現(xiàn)在的JavaEE服務(wù)器具有大量的類加載器。
在JavaDoc中對NoClassDefFoundError的產(chǎn)生是由于JVM或者類加載器實例嘗試加載類型的定義,但是該定義卻沒有找到,影響了執(zhí)行路徑。換句話說,在編譯時這個類是能夠被找到的,但是在執(zhí)行時卻沒有找到。
這一刻IDE是沒有出錯提醒的,但是在運行時卻出現(xiàn)了錯誤。
NoSuchMethodError
在另一個場景中,我們可能遇到了另一個錯誤,也就是NoSuchMethodError。
NoSuchMethodError代表這個類型確實存在,但是一個不正確的版本被加載了。
ClassCastException
ClassCastException,在一個類加載器的情況下,一般出現(xiàn)這種錯誤都會是在轉(zhuǎn)型操作時,比如:A a = (A) method();,很容易判斷出來method()方法返回的類型不是類型A,但是在 JavaEE 多個類加載器的環(huán)境下就會出現(xiàn)一些難以定位的情況。