1.我們先通過工具去編寫 .java代碼。然后通過 javac 編譯為 .class字節(jié)碼文件听皿。
2.類加載器會把 .class字節(jié)碼文件 加載到 jvm 的工作內(nèi)存中。
3.接下來jvm 的解析器去執(zhí)行我們的代碼挟鸠。
類的加載主要過程
加載 -> 連接(驗證 - > 準(zhǔn)備 -> 解析)-> 初始化 -> 使用 -> 卸載
注意:只有使用到的class類膝晾,在當(dāng)前jvm類加載器中,找不到的時候胸完,才會觸發(fā)類的加載书释。
1.創(chuàng)建對象
2.訪問靜態(tài)屬性,或者調(diào)用靜態(tài)方法
3.如果初始化一個類的時候赊窥,發(fā)現(xiàn)他的父類還沒有初始化爆惧,那么必須先初始化他的父類
4.包含 "main()" 方法的主類,必須先初始化
1.加載
就是把 編譯好的.class 文件锨能,根據(jù)全限定名稱扯再,加載到j(luò)vm的方法區(qū)中,并且生成一個 java.lang.Class對象址遇,作為方法區(qū)這些數(shù)據(jù)的訪問入口(對于HotSpot虛擬機而言熄阻,Class 對象比較特殊,雖是對象倔约,但是存放在方法區(qū))秃殉。
加載途徑:
1.從壓縮包中獲取。(jar)
2.從網(wǎng)絡(luò)中獲榷逯辍(Applet)
3.動態(tài)代理
4.jsp文件生成對應(yīng)的class文件
5.數(shù)據(jù)庫讀复濒。
這里也要注意一點脖卖,如果這個類是數(shù)組乒省,有些不同。
數(shù)組會由虛擬機進行創(chuàng)建畦木,本身就是不通過類加載進行創(chuàng)建的袖扛。
如果是引用數(shù)組,那么會遞歸采用正常的加載過程進行加載這個引用數(shù)組的類型組件。
如果是基本類型數(shù)組蛆封,會標(biāo)記與引導(dǎo)類加載器關(guān)聯(lián)唇礁。
數(shù)組的可見性將默認為public。
2.驗證
就是根據(jù) java虛擬機的規(guī)范惨篱,對加載進來的 .class 文件盏筐,進行檢驗,包括 文件格式驗證砸讳、元數(shù)據(jù)驗證琢融、字節(jié)碼驗證和符號引用驗證。比如調(diào)用到別的類的私有方法簿寂,重寫了 final修飾的方法等等漾抬,檢驗合格后,后續(xù)才能交給jvm去執(zhí)行常遂。
3.準(zhǔn)備
在準(zhǔn)備的階段中纳令,會給當(dāng)前類的靜態(tài)變量(staic 修飾)分配內(nèi)存空間,對于基本變量會給一個初始值(0 或 null)克胳,對于當(dāng)前類的靜態(tài)常量平绩,這個時候,會直接進行賦值操作漠另。
tip:
1.訪問一個類的靜態(tài)常量的時候馒过,靜態(tài)代碼塊不會執(zhí)行。
2.對于一般的成員變量是在類實例化的時候酗钞,隨對象一起分配到堆內(nèi)存中腹忽。
4.解析
指檢查類是否引用了其他的類、接口砚作,包括類或接口窘奏、字段、類方法葫录、接口方法的解析着裹,就是把符號引用替換為直接引用。
public void gotoWork(){
car.run(); //這段代碼在Worker類中的二進制表示為符號引用
}
在Worker類的二進制數(shù)據(jù)中米同,包含了一個對Car類的run()方法的符號引用骇扇,它由run()方法的全名 和 相關(guān)描述符組成。在解析階段面粮,Java虛擬機 會把這個符號引用替換為一個指針少孝,該指針指向Car類的run()方法在方法區(qū)的內(nèi)存位置,這個指針就是直接引用熬苍。
5.初始化
類初始化是類加載的最后一步稍走,除了加載階段袁翁,用戶可以通過自定義類加載器參與,其他階段都完全由虛擬機主導(dǎo)可控制婿脸。到了初始化階段才真正的執(zhí)行java代碼粱胜。
在這個階段,會執(zhí)行此類中的靜態(tài)代碼塊狐树,并執(zhí)行靜態(tài)變量賦值操作焙压。
public class Test{
public static String p1 = Test2.P2;
public static String p3 ;
static{
p3 = "p3";
}
}
6.使用
就是正常使用,如創(chuàng)建對象 或者 訪問屬性抑钟、方法冗恨。
7.卸載
如果以上三個條件全部滿足,jvm就會在方法區(qū)垃圾回收的時候?qū)︻愡M行卸載味赃,類的卸載過程其實就是在方法區(qū)中清空類信息掀抹,java類的整個生命周期就 結(jié)束了。
1心俗、該類所有的實例都已經(jīng)被回收傲武,也就是java堆中不存在該類的任何實例。
2城榛、加載該類的ClassLoader已經(jīng)被回收揪利。
3、該類對應(yīng)的java.lang.Class對象沒有任何地方被引用狠持,無法在任何地方通過反射訪問該類的方法疟位。
類加載器和雙親委派機制
- 對于任意一個類,都需要由加載它的類加載器和這個類本身來一起確立其在Java虛擬機中的唯一性喘垂。
1.啟動類加載器 bootstrap ClassLoader 第一層
他主要是負載加載jave安裝目錄下 “JAVA_HOME/lib”下的核心類甜刻。或者被 -Xbootclasspath 參數(shù)指定的路徑中的正勒,并且是虛擬機識別的(僅按照名稱得院,如 rt.jar 名字不符合的類庫,即使放到 lib 目錄下也不會重載)章贞。
2.擴展類加載器 Extension ClassLoader 第二層
這個類加載器由sun.misc.Launcher$ExtClassLoader實現(xiàn)祥绞,它負責(zé)加載JAVA_HOME/lib/ext 目錄下的,或者被java.ext.dirs 系統(tǒng)變量所指定的路徑種的所有類庫鸭限。開發(fā)者可以直接使用擴展類加載器蜕径。
3.應(yīng)用加載器 Application ClassLoader 第三層
這個類加載器由sun.misc.launcher$AppClassLoader 實現(xiàn), 他負責(zé)加載用戶類路徑(classPath)上所指的類庫。由于這個類是 getSystemClassLoader方法的返回值败京,所以也稱為 系統(tǒng)類加載器兜喻。
主要是負責(zé)夾雜 “ClassPath”環(huán)境變量所指定的類。其實就是加載我們編寫的類喧枷。
4.自定義加載器
我們也可以根據(jù)需要虹统,自己實現(xiàn)類加載器。比如 可以對字節(jié)碼進行加密隧甚,然后在自定義加載器 進行類加載的時候车荔,解密。
5. 雙親委派機制
當(dāng)應(yīng)用類需要加載一個類的時候戚扳,會先委派自己的父類加載器物種加載忧便,若父類不存在,則繼續(xù)向上委派帽借。若最終父類加載器珠增,沒能加載到,才會自己去加載砍艾。這樣做的好處蒂教,1
1.安全,避免重新定義核心類脆荷,導(dǎo)致服務(wù)異常的問題出現(xiàn)凝垛。
2.避免重復(fù)加載同一個類。
雙親委派模型的破壞者-線程上下文類加載器
在Java應(yīng)用中存在這個很多服務(wù)提供者接口( Service provider interface, SPI), 這些接口允許第三方為他們提供實現(xiàn)蜓谋,如常見的SPI 有 JDBC梦皮、JNDI等,這些SPI的接口屬于 Java核心庫桃焕,一般存在在 rt.jar包中剑肯,由 BootStrap類加載,而以來的第三方實現(xiàn)代碼的jar ,存放在classPath 下观堂,所以雙親委派的機制下让网,bootStrap加載器,無法直接去加載师痕。
從jdk 1.2 開始已入了 線程上下文類加載器(contextClassLoader),我們可以通過 java.lang.Thead 類中的
getContextLoader() 和 setContextClassLoader(ClassLoader cl) 方法來獲取和設(shè)置寂祥,如果沒有設(shè)置線程的上下文加載器,線程將繼承其父線程的類加載器七兜,初始線程的上下文類加載器就是系統(tǒng)類加載器(AppClassLoader)丸凭。在線程運行的代碼中可以通過此類加載器來加載資源。這種方式就破壞了 '雙親委派模型'腕铸。
Tomcat 的類加載器是怎么設(shè)計破壞雙親委派的惜犀?
首先,我們來問個問題:
Tomcat 如果使用默認的類加載機制行不行狠裹?
我們思考一下:Tomcat是個web容器虽界, 那么它要解決什么問題:
一個web容器可能需要部署兩個應(yīng)用程序,不同的應(yīng)用程序可能會依賴同一個第三方類庫的不同版本涛菠,不能要求同一個類庫在同一個服務(wù)器只有一份莉御,因此要保證每個應(yīng)用程序的類庫都是獨立的撇吞,保證相互隔離。
部署在同一個web容器中相同的類庫相同的版本可以共享礁叔。否則牍颈,如果服務(wù)器有10個應(yīng)用程序,那么要有10份相同的類庫加載進虛擬機琅关,這是扯淡的煮岁。
web容器也有自己依賴的類庫,不能于應(yīng)用程序的類庫混淆涣易』基于安全考慮,應(yīng)該讓容器的類庫和程序的類庫隔離開來新症。
web容器要支持jsp的修改步氏,我們知道,jsp 文件最終也是要編譯成class文件才能在虛擬機中運行徒爹,但程序運行后修改jsp已經(jīng)是司空見慣的事情戳护,否則要你何用? 所以瀑焦,web容器需要支持 jsp 修改后不用重啟腌且。
再看看我們的問題:Tomcat 如果使用默認的類加載機制行不行?
答案是不行的榛瓮。為什么铺董?我們看,第一個問題禀晓,如果使用默認的類加載器機制精续,那么是無法加載兩個相同類庫的不同版本的,默認的累加器是不管你是什么版本的粹懒,只在乎你的全限定類名重付,并且只有一份。第二個問題凫乖,默認的類加載器是能夠?qū)崿F(xiàn)的确垫,因為他的職責(zé)就是保證唯一性。第三個問題和第一個問題一樣帽芽。我們再看第四個問題删掀,我們想我們要怎么實現(xiàn)jsp文件的熱修改(樓主起的名字),jsp 文件其實也就是class文件导街,那么如果修改了披泪,但類名還是一樣,類加載器會直接取方法區(qū)中已經(jīng)存在的搬瑰,修改后的jsp是不會重新加載的款票。那么怎么辦呢控硼?我們可以直接卸載掉這jsp文件的類加載器,所以你應(yīng)該想到了艾少,每個jsp文件對應(yīng)一個唯一的類加載器卡乾,當(dāng)一個jsp文件修改了,就直接卸載這個jsp類加載器姆钉。重新創(chuàng)建類加載器说订,重新加載jsp文件抄瓦。
Tomcat 如何實現(xiàn)自己獨特的類加載機制潮瓶?
所以,Tomcat 是怎么實現(xiàn)的呢钙姊?牛逼的Tomcat團隊已經(jīng)設(shè)計好了毯辅。我們看看他們的設(shè)計圖:
我們看到,前面3個類加載和默認的一致煞额,CommonClassLoader思恐、CatalinaClassLoader、SharedClassLoader和WebappClassLoader則是Tomcat自己定義的類加載器膊毁,它們分別加載/common/胀莹、/server/、/shared/(在tomcat 6之后已經(jīng)合并到根目錄下的lib目錄下)和/WebApp/WEB-INF/中的Java類庫婚温。其中WebApp類加載器和Jsp類加載器通常會存在多個實例描焰,每一個Web應(yīng)用程序?qū)?yīng)一個WebApp類加載器,每一個JSP文件對應(yīng)一個Jsp類加載器栅螟。
commonLoader:Tomcat最基本的類加載器荆秦,加載路徑中的class可以被Tomcat容器本身以及各個Webapp訪問;
catalinaLoader:Tomcat容器私有的類加載器力图,加載路徑中的class對于Webapp不可見步绸;
sharedLoader:各個Webapp共享的類加載器,加載路徑中的class對于所有Webapp可見吃媒,但是對于Tomcat容器不可見瓤介;
WebappClassLoader:各個Webapp私有的類加載器,加載路徑中的class只對當(dāng)前Webapp可見赘那;
從圖中的委派關(guān)系中可以看出:
CommonClassLoader能加載的類都可以被Catalina ClassLoader和SharedClassLoader使用惑朦,從而實現(xiàn)了公有類庫的共用,而CatalinaClassLoader和Shared ClassLoader自己能加載的類則與對方相互隔離漓概。
WebAppClassLoader可以使用SharedClassLoader加載到的類漾月,但各個WebAppClassLoader實例之間相互隔離。
而JasperLoader的加載范圍僅僅是這個JSP文件所編譯出來的那一個.Class文件胃珍,它出現(xiàn)的目的就是為了被丟棄:當(dāng)Web容器檢測到JSP文件被修改時梁肿,會替換掉目前的JasperLoader的實例蜓陌,并通過再建立一個新的Jsp類加載器來實現(xiàn)JSP文件的HotSwap功能。
好了吩蔑,至此钮热,我們已經(jīng)知道了tomcat為什么要這么設(shè)計,以及是如何設(shè)計的烛芬,那么隧期,tomcat 違背了java 推薦的雙親委派模型了嗎?答案是:違背了赘娄。 我們前面說過:
雙親委派模型要求除了頂層的啟動類加載器之外仆潮,其余的類加載器都應(yīng)當(dāng)由自己的父類加載器加載。
很顯然遣臼,tomcat 不是這樣實現(xiàn)性置,tomcat 為了實現(xiàn)隔離性,沒有遵守這個約定揍堰,每個webappClassLoader加載自己的目錄下的class文件鹏浅,不會傳遞給父類加載器。
我們擴展出一個問題:如果tomcat 的 Common ClassLoader 想加載 WebApp ClassLoader 中的類屏歹,該怎么辦隐砸?
看了前面的關(guān)于破壞雙親委派模型的內(nèi)容,我們心里有數(shù)了蝙眶,我們可以使用線程上下文類加載器實現(xiàn)季希,使用線程上下文加載器,可以讓父類加載器請求子類加載器去完成類加載的動作械馆。