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