前言
在 Java 應用中咽笼,常用的 Web 服務器一般由 tomcat顷编、weblogic、jetty剑刑、undertwo等媳纬。但從 Java 2019和2020 生態(tài)使用報告可以看到,tomcat的用戶量對比明顯較大施掏,當然這也基于它開源和免費的特點钮惠。
從軟件架構的發(fā)展角度來看,軟件架構大致經(jīng)歷了如下幾個階段:
從 Java Web 角度來說其监,架構大致經(jīng)歷了:
從當前企業(yè)使用的架構角度來說萌腿,使用SSM架構項目比較多限匣,SSH基本被淘汰(大部分是老項目維護)抖苦,很大一部分企業(yè)轉向微服務架構了。
基于Spring 生態(tài)來說米死,大部分中小型企業(yè)都基本使用SpringBoot锌历,SpringBoot本身集成了 tomcat、jetty和undertwo 容器峦筒,那么我們?yōu)槭裁葱枰〞r間來研究tomcat呢究西?
- 當前tomcat依然是主流java web容器,研究它符合java 技術生態(tài)發(fā)展物喷;
- 在java web項目調(diào)優(yōu)中卤材,如ssm項目中,在優(yōu)化項目時峦失,jvm和tomcat同樣重要扇丛,都需要優(yōu)化;
- 盡管springboot內(nèi)置了tomcat容器尉辑,且配置了默認的tomcat參數(shù)帆精,但當默認的tomcat參數(shù)滿足不了項目優(yōu)化要求時,就需要優(yōu)化人員手動進行相關的參數(shù)優(yōu)化隧魄,因此研究tomcat非常必要卓练;
- 熟悉tomcat架構,是后續(xù)進行項目優(yōu)化的基礎购啄,也是必備條件襟企。
Tomcat架構說明
知識點:
- Tomcat目錄結構
- Tomcat簡要架構
- Tomcat各組件及關系
- Tomcat server.xml配置詳解
- Tomcat啟動參數(shù)說明(啟動腳本)
Tomcat
是一個基于JAVA的WEB容器,其實現(xiàn)了JAVA EE中的 Servlet 與 jsp 規(guī)范狮含,與Nginx Apache 服務器不同在于一般用于動態(tài)請求處理整吆。在架構設計上采用面向組件的方式設計拱撵。即整體功能是通過組件的方式拼裝完成。另外每個組件都可以被替換以保證靈活性表蝙。
通過Tomcat官方可以看到拴测,目前已經(jīng)更新到Tomcat 10了,但當前大部分企業(yè)使用的Tomcat 為8或者9版本府蛇。
Tomcat 目錄結構
- bin:可執(zhí)行文件集索,.sh結尾的表示linux可執(zhí)行文件,.bat結尾的表示windows可執(zhí)行文件
- conf:配置文件
- lib:tomcat相關jar包
- temp:臨時文件
- webapps:存放項目
- work:工作目錄
bin目錄
bin目錄存放可執(zhí)行文件汇跨,簡要結束常用命令
這里主要解釋如下通用的命令务荆,其他命令就不一一介紹
- catalina.sh 真正啟動Tomcat文件,可以在里面設置jvm參數(shù)
- startup.sh 程序項目命令文件
- version.sh 查看tomcat版本相關信息命令文件
- shutdown.sh 關閉程序命令
conf目錄
conf文件夾用來存放tomcat相關配置文件
1.catalina.policy
項目安全文件穷遂,用來防止欺騙代碼或JSP執(zhí)行帶有像System.exit(0)這樣的命令的可能影響容器的破壞性代碼. 只有當Tomcat用-security命令行參數(shù)啟動時這個文件才會被使用函匕,即啟動tomcat時, startup.sh -security
蚪黑。
上圖中盅惜,tomcat容器下部署兩個項目,項目1和項目2忌穿。由于項目1中有代碼System.exit(0)抒寂,當訪問該代碼時,該代碼會導致整個tomcat停止掠剑,從而也導致項目2停止屈芜。
為了解決因項目1存在欺騙代碼或不安全代碼導致?lián)p害Tomcat容器,從而影響其他項目正常運行的問題朴译,啟動tomcat容器時井佑,加上-security參數(shù)就,即startup.sh -security
眠寿,如此即使項目1中有代碼System.exit(0)躬翁,也只會僅僅停止項目1,而不會影響Tomcat容器澜公,然而起作用的配置文件就是catalina.policy文件姆另。
2.catalina.properties
配置tomcat啟動相關信息文件
3.context.xml
監(jiān)視并加載資源文件,當監(jiān)視的文件發(fā)生發(fā)生變化時坟乾,自動加載
4.jaspic-providers.xml 和 jaspic-providers.xsd
這兩個文件不常用
5.logging.properties
該文件為tomcat日志文件迹辐,包括配置tomcat輸出格式,日志級別等
6.server.xml
tomcat核心架構主件文件甚侣,下面會詳細解析明吩。
7.tomcat-users.xml和tomcat-users.xsd
tomcat用戶文件,如配置遠程登陸賬號
tomcat-users.xsd 為tomcat-users.xml描述和約束文件
8.web.xml
tomcat全局配置文件殷费。
lib目錄
lib文件夾主要用來存放tomcat依賴jar包印荔,如下為 tomcat 的lib文件夾下的相關jar包低葫。
每個jar包功能,這里就不講解了仍律,這里主要分析ecj-4.13.jar嘿悬,這個jar包起到將.java編譯成.class字節(jié)碼作用。
假設要編譯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日志文件草则,大致包括如下六類文件:
catalina.date.log | 表示tomcat啟動文件 |
---|---|
catalina.out | 表示catalina.date.log日志匯總 |
host-manager.date.log | 表示訪問webapps下host-manager項目日志钢拧,如訪問 ip:8080/host-manager/html |
localhost.date.log | 表示tomcat在啟動時,自身訪問服務炕横,這個日志只記錄tomcat訪問日志源内,而非業(yè)務項目日志 |
localhost_access_log.date.txt | 表示訪問tomcat所有項目的日志記錄,如下表示訪問項目localhost,host-manager.html,manager.html和test/index.html四個項目日志記錄 |
manager.date.log | 表示訪問webapps下manager項目日志份殿,如訪問 ip:8080/manager/html |
temp目錄
temp目錄用戶存放tomcat在運行過程中產(chǎn)生的臨時文件膜钓。(清空不會對tomcat運行帶來影響)。
webapps目錄
webapps目錄用來存放應用程序伯铣,當tomcat啟動時會去加載webapps目錄下的應用程序呻此÷秩遥可以以文件夾腔寡、war包、jar包的形式發(fā)布應用掌唾。
當然放前,你也可以把應用程序放置在磁盤的任意位置,在配置文件中映射好就行糯彬。
work目錄
work目錄用來存放tomcat在運行時的編譯后文件凭语,例如JSP編譯后的文件。
清空work目錄撩扒,然后重啟tomcat似扔,可以達到清除緩存的作用。
Tomcat 簡要架構
Tomcat 各組件及關系
- Server 和 Service
- Connector 連接器
- HTTP 1.1
- SSL https
- AJP( Apache JServ Protocol) apache 私有協(xié)議搓谆,用于apache 反向代理Tomcat
- Container
- Engine 引擎 catalina
- Host 虛擬機 基于域名 分發(fā)請求
- Context 隔離各個WEB應用 每個Context的 ClassLoader都是獨立
- Component
- Manager (管理器)
- logger (日志管理)
- loader (載入器)
- pipeline (管道)
- valve (管道中的閥)
Tomcat server.xml 配置詳解
Server 的基本基本配置:
<Server>
<Listener /><!-- 監(jiān)聽器 -->
<GlobaNamingResources> <!-- 全局資源 -->
</GlobaNamingResources
<Service> <!-- 服務 用于 綁定 連接器與 Engine -->
<Connector 8080/> <!-- 連接器-->
<Connector 8010 /> <!-- 連接器-->
<Connector 8030/> <!-- 連接器-->
<Engine> <!-- 執(zhí)行引擎-->
<Logger />
<Realm />
<host "www.test.com" appBase=""> <!-- 虛擬主機-->
<Logger /> <!-- 日志配置-->
<Context "/applction" path=""/> <!-- 上下文配置-->
</host>
</Engine>
</Service>
</Server>
server
root元素:server 的頂級配置
主要屬性:
port:執(zhí)行關閉命令的端口號
shutdown:關閉命令
- 演示shutdown的用法
#基于telent 執(zhí)行SHUTDOWN 命令即可關閉
telent 127.0.0.1 8005
SHUTDOWN
service
服務:將多個connector 與一個Engine組合成一個服務炒辉,可以配置多個服務。
Connector
連接器:用于接收 指定協(xié)議下的連接 并指定給唯一的Engine 進行處理泉手。
主要屬性:
- protocol 監(jiān)聽的協(xié)議黔寇,默認是http/1.1
- port 指定服務器端要創(chuàng)建的端口號
- minThread 服務器啟動時創(chuàng)建的處理請求的線程數(shù)
- maxThread 最大可以創(chuàng)建的處理請求的線程數(shù)
- enableLookups 如果為true,則可以通過調(diào)用request.getRemoteHost()進行DNS查詢來得到遠程客戶端的實際主機名斩萌,若為false則不進行DNS查詢缝裤,而是返回其ip地址
- redirectPort 指定服務器正在處理http請求時收到了一個SSL傳輸請求后重定向的端口號
- acceptCount 指定當所有可以使用的處理請求的線程數(shù)都被使用時屏轰,可以放到處理隊列中的請求數(shù),超過這個數(shù)的請求將不予處理憋飞,默認100霎苗;
- address 綁定客戶端特定地址,127.0.0.1
- bufferSize 每個請求的緩沖區(qū)大小 bufferSize * maxThreads
- compression 是否啟用文檔壓縮
- compressionMinSize 文檔壓縮的最小大小
- compressableMimeTypes text/html,text/xml,text/plain
- connectionTimeout 客戶端發(fā)起鏈接到服務端接收為止榛做,指定超時的時間數(shù)(以毫秒為單位)
- connectionUploadTimeout upload情況下連接超時時間
- disableUploadTimeout 如果為true則使用 connectionTimeout
- keepAliveTimeout 當長鏈接閑置 指定時間主動關閉 鏈接 叨粘,前提是客戶端請求頭 帶上這個 head"connection" " keep-alive"
- maxKeepAliveRequests 最大的 長連接數(shù) 默認最大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í)行器轰传,默認的引擎是catalina。一個service 中只能配置一個Engine获茬。
主要屬性:name 引擎名稱 defaultHost 默認host
Host
虛擬機:基于域名匹配至指定虛擬機港庄。類似于nginx 當中的server,默認的虛擬機是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
應用上下文:一個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過程是怎么樣的调鬓?
- 復制WAR包至Tomcat webapp 目錄艇炎。
- 執(zhí)行starut.bat 腳本啟動腾窝。
- 啟動過程中war 包會被自動解壓裝載。
但是我們在Eclipse 或idea 中啟動WEB項目的時候 也是把War包復雜至webapps 目錄解壓嗎虹脯?顯然不是,其真正做法是在Tomcat程序文件之外創(chuàng)建了一個部署目錄托慨,在一般生產(chǎn)環(huán)境中也是這么做的 即:Tomcat 程序目錄和部署目錄分開 。
我們只需要在啟動時指定CATALINA_HOME 與 CATALINA_BASE 參數(shù)即可實現(xiàn)厚棵。
啟動參數(shù) | 描述說明 |
---|---|
JAVA_OPTS | jvm 啟動參數(shù) , 設置內(nèi)存 編碼等 -Xms100m -Xmx200m -Dfile.encoding=UTF-8 |
JAVA_HOME | 指定jdk 目錄,如果未設置從java 環(huán)境變量當中去找婆硬。 |
CATALINA_HOME | Tomcat 程序根目錄 |
CATALINA_BASE | 應用部署目錄,默認為$CATALINA_HOME |
CATALINA_OUT | 應用日志輸出目錄:默認$CATALINA_BASE/log |
CATALINA_TMPDIR | 應用臨時目錄:默認:$CATALINA_BASE/temp |
可以編寫一個腳本 來實現(xiàn)自定義配置:
更新 啟動 腳本:
#!/bin/bash
export JAVA_OPTS="-Xms100m -Xmx200m"
export JAVA_HOME=/root/svr/jdk/
export CATALINA_HOME=/root/svr/apache-tomcat-7.0.81
export CATALINA_BASE="`pwd`"
case $1 in
start)
$CATALINA_HOME/bin/catalina.sh start
echo start success!!
;;
stop)
$CATALINA_HOME/bin/catalina.sh stop
echo stop success!!
;;
restart)
$CATALINA_HOME/bin/catalina.sh stop
echo stop success!!
sleep 3
$CATALINA_HOME/bin/catalina.sh start
echo start success!!
;;
version)
$CATALINA_HOME/bin/catalina.sh version
;;
configtest)
$CATALINA_HOME/bin/catalina.sh configtest
;;
esac
exit 0
自動部署腳本:
#!/bin/bash -e
export now_time=$(date +%Y-%m-%d_%H-%M-%S)
echo "deploy time:$now_time"
app=$1
version=$2
mkdir -p war/
#從svn下載程序至 war目錄
war=war/${app}_${version}.war
echo "$war"
svn export svn://192.168.0.253/release/${app}_${version}.war $war
deploy_war() {
#解壓版本至當前目錄
target_dir=war/${app}_${version}_${now_time}
unzip -q $war -d $target_dir
rm -f appwar
ln -sf $target_dir appwar
target_ln=`pwd`/appwar
echo '<?xml version="1.0" encoding="UTF-8" ?>
<Context docBase="'$target_ln'" allowLinking="false">
</Context>' > conf/Catalina/localhost/ROOT.xml
#重啟Tomcat服務
./tomcat.sh restart
}
deploy_war
Tomcat 網(wǎng)絡通信模型剖析
Tomcat 支持四種線程模型介紹
什么是IO?
IO是指為數(shù)據(jù)傳輸所提供的輸入輸出流向楼,其輸入輸出對象可以是:文件谐区、網(wǎng)絡服務、內(nèi)存等宋列。
什么是IO模型?
提問:
假設應用在從硬盤中讀取一個大文件過程中灭返,此時CPU會與硬盤一樣出于高負荷狀態(tài)么?
演示:
- 演示觀察大文件的讀寫過程當中CPU 有沒有發(fā)生大波動熙含。
演示結果:CPU 沒有太高的增漲
通常情況下IO操作是比較耗時的,所以為了高效的使用硬件艇纺,應用程序可以用一個專門線程進行IO操作怎静,而另外一個線程則利用CPU的空閑去做其它計算。這種為提高應用執(zhí)行效率而采用的IO操作方法即為IO模型喂饥。
各IO模型簡要說明
描述 | |
---|---|
BIO | 阻塞式IO消约,即Tomcat使用傳統(tǒng)的java.io進行操作员帮。該模式下每個請求都會創(chuàng)建一個線程导饲,對性能開銷大,不適合高并發(fā)場景渣锦。優(yōu)點是穩(wěn)定,適合連接數(shù)目小且固定架構型檀。 |
NIO | 非阻塞式IO听盖,jdk1.4 之后實現(xiàn)的新IO裂七。該模式基于多路復用選擇器監(jiān)測連接狀態(tài)在通知線程處理,從而達到非阻塞的目的背零。比傳統(tǒng)BIO能更好的支持并發(fā)性能无埃。Tomcat 8.0之后默認采用該模式 |
APR | 全稱是 Apache Portable Runtime/Apache可移植運行庫),是Apache HTTP服務器的支持庫嫉称≌煺颍可以簡單地理解為,Tomcat將以JNI的形式調(diào)用Apache HTTP服務器的核心動態(tài)鏈接庫來處理文件讀取或網(wǎng)絡傳輸操作织阅。使用需要編譯安裝APR 庫 |
AIO | 異步非阻塞式IO虽缕,jdk1.7后之支持 。與nio不同在于不需要多路復用選擇器蒲稳,而是請求處理線程執(zhí)行完程進行回調(diào)調(diào)知氮趋,已繼續(xù)執(zhí)行后續(xù)操作江耀。Tomcat 8之后支持。 |
使用指定IO模型的配置方式:
配置 server.xml 文件當中的 <Connector protocol="HTTP/1.1"> 修改即可祥国。
默認配置 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ù):
每秒提交數(shù) | BIO執(zhí)行線程 | NIO執(zhí)行線程 | |
---|---|---|---|
預測 | 200 | 200線程 | 20線程 |
實驗實際 | 200 | 55 wait個線程 | 23個線程 |
模擬生產(chǎn)環(huán)境 | 200 | 229個run線程 | 20個wait 線程 |
生成環(huán)境重要因素:
- 網(wǎng)絡
- 程序執(zhí)行業(yè)務用時
源代碼地址:bit-bigdata-transmission
BIO 線程模型
BIO 源碼
線程組:
Accept 線程組 acceptorThreadCount 默認1個
exec 線程組 maxThread
JIoEndpoint
Acceptor extends Runnable
SocketProcessor extends Runnable
NIO 線程模型
NIO 線程模型
Accept 線程組 默認兩個輪詢器
Poller Selector PollerEvent輪詢線程狀態(tài)
SocketProcessor
BIO
線程數(shù)量 會受到 客戶端阻塞语御、網(wǎng)絡延遲席怪、業(yè)務處理慢===>線程數(shù)會更多。
NIO
線程數(shù)量 會受到業(yè)務處理慢===>線程數(shù)會更多碉纺。
Tomcat connector 并發(fā)參數(shù)解讀
名稱 | 描述 |
---|---|
acceptCount | 等待最大隊列 |
address | 綁定客戶端特定地址,127.0.0.1 |
bufferSize | 每個請求的緩沖區(qū)大小骨田。bufferSize * maxThreads |
compression | 是否啟用文檔壓縮 |
compressableMimeTypes | text/html,text/xml,text/plain |
connectionTimeout | 客戶發(fā)起鏈接 到 服務端接收為止,中間最大的等待時間 |
connectionUploadTimeout | upload 情況下連接超時時間 |
disableUploadTimeout | true 則使用connectionTimeout |
enableLookups | 禁用DNS查詢 true |
keepAliveTimeout | 當長鏈接閑置 指定時間主動關閉 鏈接 碎节,前提是客戶端請求頭 帶上這個 head"connection" " keep-alive" |
maxKeepAliveRequests | 最大的 長連接數(shù) |
maxHttpHeaderSize | |
maxSpareThreads | BIO 模式下 最多線閑置線程數(shù) |
maxThreads(執(zhí)行線程) | 最大執(zhí)行線程數(shù) |
minSpareThreads(初始線業(yè)務線程 10) | BIO 模式下 最小線閑置線程數(shù) |
Tomcat 類加載機制源碼解析
類加載的本質(zhì)
是用來加載 Class 的抵卫。它負責將 Class 的字節(jié)碼形式轉換成內(nèi)存形式的 Class 對象。字節(jié)碼可以來自于磁盤文件 _.class殖氏,也可以是 jar 包里的 _.class姻采,也可以來自遠程服務器提供的字節(jié)流,字節(jié)碼的本質(zhì)就是一個字節(jié)數(shù)組 []byte慨亲,它有特定的復雜的內(nèi)部格式。
JVM 運行實例中會存在多個 ClassLoader巴刻,不同的 ClassLoader 會從不同的地方加載字節(jié)碼文件蛉签。它可以從不同的文件目錄加載,也可以從不同的 jar 文件中加載碍舍,也可以從網(wǎng)絡上不同的靜態(tài)文件服務器來下載字節(jié)碼再加載。
jvm里ClassLoader的層次結構
類加載器層次結構
BootstrapClassLoader(啟動類加載器)
稱為啟動類加載器妈经,是Java類加載層次中最頂層的類加載器锻全,負責加載JDK中的核心類庫,如:rt.jar、resources.jar妈踊、charsets.jar等,可通過如下程序獲得該類加載器從哪些地方加載了相關的jar或class文件:
URL[] urLs = sun.misc.Launcher.getBootstrapClassPath().getURLs();
for (URL url : urLs) {
System.out.println(url.toExternalForm());
}
程序執(zhí)行結果如下:
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í)行結果如下:
null
可知由于BootstrapClassLoader對Java不可見,所以返回了null,我們也可以通過某一個類的加載器是否為null來作為判斷該類是不是使用BootstrapClassLoader進行加載的依據(jù)歪泳。
ExtensionClassLoader
ExtClassLoader稱為擴展類加載器,主要負責加載Java的擴展類庫,默認加載JAVA_HOME/jre/lib/ext/目錄下的所有jar包或者由java.ext.dirs系統(tǒng)屬性指定的jar包.放入這個目錄下的jar包對AppClassLoader加載器都是可見的(因為ExtClassLoader是AppClassLoader的父加載器,并且Java類加載器采用了委托機制)敌卓。
ExtClassLoader的類掃描路徑通過執(zhí)行下面代碼來看一下:
String extDirs = System.getProperty("java.ext.dirs");
for (String path : extDirs.split(";")) {
System.out.println(path);
}
執(zhí)行結果如下(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
從上面的程序運行結果可知ExtClassLoader的父加載器為null伶氢,之前說過BootstrapClassLoader對Java不可見,所以返回了null。ExtClassLoader的父加載器返回的是null,那是否說明ExtClassLoader的父加載器是BootstrapClassLoader?
Bootstrap ClassLoader是由C/C++編寫的癣防,它本身是虛擬機的一部分,所以它并不是一個JAVA類幕屹,也就是無法在java代碼中獲取它的引用级遭,JVM啟動時通過Bootstrap類加載器加載rt.jar等核心jar包中的class文件,之前的int.class,String.class都是由它加載挫鸽。然后呢,我們前面已經(jīng)分析了像云,JVM初始化sun.misc.Launcher并創(chuàng)建Extension ClassLoader和AppClassLoader實例蚂夕。并將ExtClassLoader設置為AppClassLoader的父加載器。Bootstrap沒有父加載器婿牍,但是它卻可以作用一個ClassLoader的父加載器。比如ExtClassLoader俏蛮。這也可以解釋之前通過ExtClassLoader的getParent方法獲取為Null的現(xiàn)象
AppClassLoader
才是直接面向我們用戶的加載器搏屑,它會加載 Classpath 環(huán)境變量里定義的路徑中的 jar 包和目錄伟骨。我們自己編寫的代碼以及使用的第三方 jar 包通常都是由它來加載的逛腿。
加載System.getProperty("java.class.path")所指定的路徑或jar雕凹。在使用Java運行程序時汽摹,也可以加上-cp來覆蓋原有的Classpath設置拉庶,例如: java -cp ./lavasoft/classes HelloWorld
public class AppClassLoaderTest {
public static void main(String[] args) {
System.out.println(ClassLoader.getSystemClassLoader());
}
}
輸出結果如下:
sun.misc.Launcher$AppClassLoader@18b4aac2
以上結論說明調(diào)用ClassLoader.getSystemClassLoader()
可以獲得AppClassLoader類加載器站绪。
protected ClassLoader() {
this(checkCreateClassLoader(), getSystemClassLoader());
}
通過查看ClassLoader的源碼發(fā)現(xiàn)并且在沒有特定說明的情況下,用戶自定義的任何類加載器都將該類加載器作為自定義類加載器的父加載器搁进。
通過執(zhí)行上面的代碼即可獲得classpath的加載路徑峻堰。
在上面的main函數(shù)的類的加載就是使用AppClassLoader加載器進行加載的,可以通過執(zhí)行下面的代碼得出這個結論
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í)行結果如下:
sun.misc.Launcher$AppClassLoader@18b4aac2
sun.misc.Launcher$ExtClassLoader@2d209079
從上面的運行結果可以得知AppClassLoader的父加載器是ExtClassLoader
Tomcat的 類加載順序
在Tomcat中贺归,默認的行為是先嘗試在Bootstrap和Extension中進行類型加載剑勾,如果加載不到則在Webapp ClassLoader中進行加載洲赵,如果還是找不到則在Common中進行查找格郁。
NoClassDefFoundError
NoClassDefFoundError是在開發(fā)JavaEE程序中常見的一種問題自沧。該問題會隨著你所使用的JavaEE中間件環(huán)境的復雜度以及應用本身的體量變得更加復雜,尤其是現(xiàn)在的JavaEE服務器具有大量的類加載器雨效。
在JavaDoc中對NoClassDefFoundError的產(chǎn)生是由于JVM或者類加載器實例嘗試加載類型的定義,但是該定義卻沒有找到传透,影響了執(zhí)行路徑极颓。換句話說,在編譯時這個類是能夠被找到的菠隆,但是在執(zhí)行時卻沒有找到。
這一刻IDE是沒有出錯提醒的躯肌,但是在運行時卻出現(xiàn)了錯誤破衔。
NoSuchMethodError
在另一個場景中,我們可能遇到了另一個錯誤晰筛,也就是NoSuchMethodError拴袭。
NoSuchMethodError代表這個類型確實存在曙博,但是一個不正確的版本被加載了。
ClassCastException
ClassCastException羊瘩,在一個類加載器的情況下盼砍,一般出現(xiàn)這種錯誤都會是在轉型操作時,比如:A a = (A) method();睬捶,很容易判斷出來method()方法返回的類型不是類型A近刘,但是在 JavaEE 多個類加載器的環(huán)境下就會出現(xiàn)一些難以定位的情況。
部分圖片來源于網(wǎng)絡觉渴,版權歸原作者,侵刪座韵。