類加載

類的生命周期

java類執(zhí)行過程
類的生命周期圖

類加載過程包括:加載-驗(yàn)證-準(zhǔn)備-解析-初始化链快。
這個(gè)過程順序并不是固定的凉敲,最多僅僅代表它們開始的順序着倾,實(shí)際上這五個(gè)過程是交叉進(jìn)行的,通常會(huì)在一個(gè)階段執(zhí)行的過程中調(diào)用令蛉、激活另外一個(gè)階段聚霜。

注意:解析階段有可能在初始化階段之后再開始。

生命周期過程詳解

  1. 加載:一珠叔、將class文件加載在內(nèi)存中蝎宇;二、將靜態(tài)數(shù)據(jù)結(jié)構(gòu)(數(shù)據(jù)存在于class文件的結(jié)構(gòu))轉(zhuǎn)化成方法區(qū)中運(yùn)行時(shí)的數(shù)據(jù)結(jié)構(gòu)(數(shù)據(jù)存在于JVM時(shí)的數(shù)據(jù)結(jié)構(gòu))祷安;三姥芥、在堆中生成一個(gè)代表這個(gè)類的java.lang.Class對(duì)象,作為數(shù)據(jù)訪問的入口汇鞭。
  2. 驗(yàn)證:確保加載的類符合JVM規(guī)范與安全凉唐。
  3. 準(zhǔn)備:為static變量在方法區(qū)中分配空間,設(shè)置變量的初始值霍骄。例如static int a=3台囱,在此階段會(huì)a被初始化為0,其他數(shù)據(jù)類型參考成員變量聲明读整。
  4. 解析:虛擬機(jī)將常量池的符號(hào)引用轉(zhuǎn)變成直接引用簿训。例如"aaa"為常量池的一個(gè)值,直接把"aaa"替換成存在于內(nèi)存中的地址米间。
    • 符號(hào)引用:符號(hào)引用以一組符號(hào)來描述所引用的目標(biāo)强品,符號(hào)可以是任何形 式的字面量,只要使用時(shí)能無歧義地定位到目標(biāo)即可屈糊。符號(hào)引用與虛擬機(jī)實(shí)現(xiàn)的內(nèi)存布局無關(guān)的榛,引用 的目標(biāo)并不一定已經(jīng)加載到內(nèi)存中。
    • 直接引用:直接引用可以是直接指向目標(biāo)的指針逻锐、相對(duì)偏移量或是一個(gè)能間接定位到目標(biāo)的句柄夫晌。直接引用是與虛擬機(jī)實(shí)現(xiàn)的內(nèi)存布局相關(guān)的雕薪,如果有了直接引用,那么引用的目標(biāo)必定已經(jīng)在內(nèi)存中存在晓淀。
  5. 初始化:初始化階段是執(zhí)行類構(gòu)造器<clinit>()方法蹦哼。在類構(gòu)造器方法中,它將由編譯器自動(dòng)收集類中的所有類變量的賦值動(dòng)作(準(zhǔn)備階段的a正是被賦值a)和靜態(tài)變量與靜態(tài)語句塊static{}合并要糊,初始化時(shí)機(jī)后續(xù)再聊。
  6. 使用:正常使用妆丘。
  7. 卸載:GC把無用對(duì)象從內(nèi)存中卸載锄俄。

類初始化時(shí)機(jī)

只有當(dāng)對(duì)類主動(dòng)使用的時(shí)候才會(huì)導(dǎo)致類的初始化。

主動(dòng)引用(發(fā)生類初始化過程)
  1. 創(chuàng)建類的實(shí)例勺拣,也就是new的方式
  2. 訪問某個(gè)類或接口的靜態(tài)變量奶赠,或者對(duì)該靜態(tài)變量賦值
  3. 調(diào)用類的靜態(tài)方法
  4. 反射(如Class.forName(“com.shengsiyuan.Test”))
  5. 初始化某個(gè)類的子類,則其父類也會(huì)被初始化
  6. Java虛擬機(jī)啟動(dòng)時(shí)被標(biāo)明為啟動(dòng)類的類(Java Test)药有,直接使用java.exe命令來運(yùn)行某個(gè)主類
被動(dòng)引用(不會(huì)發(fā)生類的初始化)
  1. 通過子類引用父類的靜態(tài)字段毅戈,只會(huì)觸發(fā)父類的初始化,而不會(huì)觸發(fā)子類的初始化愤惰。
  2. 定義對(duì)象數(shù)組苇经,不會(huì)觸發(fā)該類的初始化。
  3. 常量在編譯期間會(huì)存入調(diào)用類的常量池中宦言,本質(zhì)上并沒有直接引用定義常量的類扇单,不會(huì)觸發(fā)定義常量所在的類。
  4. 通過類名ClassName.class獲取Class對(duì)象奠旺,不會(huì)觸發(fā)類的初始化蜘澜。
  5. Class.forName("類名全路徑")加載指定類時(shí),如果指定參數(shù)initialize為false時(shí)响疚,也不會(huì)觸發(fā)類初始化鄙信,其實(shí)這個(gè)參數(shù)是告訴虛擬機(jī),是否要對(duì)類進(jìn)行初始化忿晕。
  6. 通過ClassLoader默認(rèn)的loadClass方法装诡,也不會(huì)觸發(fā)初始化動(dòng)作。

類加載器

主要的類加載器有

  1. 啟動(dòng)類加載器(Bootstrap ClassLoader):由C++實(shí)現(xiàn)杏糙,沒有父類慎王,負(fù)責(zé)加載 JAVA_HOME\lib 目錄中的,或通過-Xbootclasspath參數(shù)指定路徑中的宏侍,且被虛擬機(jī)認(rèn)可(按文件名識(shí)別赖淤,如rt.jar)的類。
  2. 擴(kuò)展類加載器(Extension ClassLoader):由Java語言實(shí)現(xiàn)谅河,父類加載器為null咱旱,負(fù)責(zé)加載 JAVA_HOME\lib\ext 目錄中的确丢,或通過java.ext.dirs系統(tǒng)變量指定路徑中的類庫。
  3. 應(yīng)用程序類加載器(Application ClassLoader):由Java語言實(shí)現(xiàn)吐限,父類加載器為ExtClassLoader鲜侥,負(fù)責(zé)加載用戶路徑(classpath)上的類庫。
  4. 自定義加載器:可以通過繼承java.lang.ClassLoader或URLClassLoader實(shí)現(xiàn)自定義的類加載器诸典,父類加載器為AppClassLoader描函。
雙親委派模式
image.png

JVM使用的是雙親委派模式的類加載機(jī)制,這種機(jī)制的好處有:

  1. 避免類的重復(fù)加載
  2. 保證核心基礎(chǔ)jar包加載的安全問題狐粱。

加載過程大致可以分為兩個(gè)過程:

  1. 查找過程舀寓,首先從收到請(qǐng)求的加載器開始,從下到上查找是否加載過該類肌蜻,從下到上的順序:自定義類加載器->應(yīng)用類加載器->拓展類加載器->啟動(dòng)類加載器互墓;
  2. 加載過程,如果找不到加載過該類蒋搜,則從上到下篡撵,從啟動(dòng)類加載器開始嘗試從自己負(fù)責(zé)的路徑下加載該類,如果加載失敗則由下級(jí)加載器加載豆挽,從上到下的順序:啟動(dòng)類加載器 -> 拓展類加載器 ->應(yīng)用類加載器 -> 自定義類加載器育谬。
加載器類圖
image.png

從類圖可以看出

  • 拓展類加載器ExtClassLoader和系統(tǒng)類加載器AppClassLoader,這兩個(gè)類都繼承自URLClassLoader祷杈,是sun.misc.Launcher的靜態(tài)內(nèi)部類斑司。
  • sun.misc.Launcher主要被系統(tǒng)用于啟動(dòng)主應(yīng)用程序,ExtClassLoader和AppClassLoader都是由sun.misc.Launcher創(chuàng)建的但汞。
  • ExtClassLoader并沒有重寫loadClass()方法宿刮,而AppClassLoader重載了loadCass()方法,但最終調(diào)用的還是父類loadClass()方法私蕾,因此依然遵守雙親委派模式僵缺。
自定義加載器

方式

  1. 繼承URLClassLoader,不需要重寫 findClass()等方法踩叭,繼承雙親委派模式磕潮。
  2. 繼承ClassLoader,只重寫findClass方法容贝,這種方式繼承雙親委派模式自脯。
  3. 繼承ClassLoader,重寫loadClass方法斤富,不使用雙親委派模式膏潮,自定義類加載模式。

自定義加載器場景:

  1. 當(dāng)class文件不在ClassPath路徑下满力,默認(rèn)系統(tǒng)類加載器無法找到該class文件焕参,在這種情況下我們需要實(shí)現(xiàn)一個(gè)自定義的ClassLoader來加載特定路徑下的class文件生成class對(duì)象轻纪。
  2. 當(dāng)一個(gè)class文件是通過網(wǎng)絡(luò)傳輸并且可能會(huì)進(jìn)行相應(yīng)的加密操作時(shí),需要先對(duì)class文件進(jìn)行相應(yīng)的解密后再加載到JVM內(nèi)存中叠纷,這種情況下也需要編寫自定義的ClassLoader并實(shí)現(xiàn)相應(yīng)的邏輯刻帚。
  3. 當(dāng)需要實(shí)現(xiàn)熱部署功能時(shí)(一個(gè)class文件通過不同的類加載器產(chǎn)生不同class對(duì)象從而實(shí)現(xiàn)熱部署功能),需要實(shí)現(xiàn)自定義ClassLoader的邏輯涩嚣。

注意:由于雙親委托機(jī)制崇众,classpath目錄下的類默認(rèn)由Application類加載器加載,所以航厚,自定義加載器如果沒有重寫loadClass方法去加載classpath下的類是不會(huì)成功的校摩,當(dāng)然,可以通過直接調(diào)findClass()繞過雙親委托來加載阶淘。

雙親委派模型的破壞者1——線程上下文加載器
image.png

在Java應(yīng)用中存在著很多服務(wù)提供者接口(Service Provider Interface,SPI)互妓,這些接口允許第三方為它們提供實(shí)現(xiàn)溪窒,如常見的 SPI 有 JDBC、JNDI等冯勉,這些 SPI 的接口屬于 Java 核心庫澈蚌,一般存在rt.jar包中,由Bootstrap類加載器加載灼狰,而 SPI 的第三方實(shí)現(xiàn)代碼則是作為Java應(yīng)用所依賴的 jar 包被存放在classpath路徑下宛瞄,由于SPI接口中的代碼經(jīng)常需要加載具體的第三方實(shí)現(xiàn)類并調(diào)用其相關(guān)方法,但SPI的核心接口類是由引導(dǎo)類加載器來加載的交胚,而Bootstrap類加載器無法直接加載SPI的實(shí)現(xiàn)類份汗,同時(shí)由于雙親委派模式的存在,Bootstrap類加載器也無法反向委托AppClassLoader加載器SPI的實(shí)現(xiàn)類蝴簇。在這種情況下杯活,我們就需要一種特殊的類加載器來加載第三方的類庫,而線程上下文類加載器就是很好的選擇熬词。

加載方式
  1. 顯式加載:指的是在代碼中通過調(diào)用ClassLoader加載class對(duì)象旁钧,比如代碼中通過Class.forName()、this.getClass.getClassLoader.LoadClass()互拾,自定義類加載器中的findClass()方法等歪今。
  2. 隱式加載:不直接在代碼中調(diào)用ClassLoader的方法加載class對(duì)象,而是通過虛擬機(jī)自動(dòng)加載到內(nèi)存中颜矿,除了顯式加載的寄猩,其它都是隱式加載。
ClassLoader關(guān)鍵方法
  • loadClass(String)或衡,該方法加載指定名稱(包括包名)的二進(jìn)制類型焦影,該方法在JDK1.2之后不再建議用戶重寫但用戶可以直接調(diào)用該方法车遂,loadClass()方法是ClassLoader類自己實(shí)現(xiàn)的,該方法中的邏輯就是雙親委派模式的實(shí)現(xiàn)斯辰,所以舶担,自定義加載器可以覆蓋loadClass方法覆蓋雙親委派模式。
  • findClass(String) 彬呻,在自定義類加載器時(shí)衣陶,會(huì)直接覆蓋ClassLoader的findClass()方法并編寫加載規(guī)則,取得要加載類的字節(jié)碼后轉(zhuǎn)換成流闸氮,然后調(diào)用defineClass()方法生成類的Class對(duì)象剪况。
  • defineClass(byte[] b, int off, int len) ,是用來將byte字節(jié)流解析成JVM能夠識(shí)別的Class對(duì)象(ClassLoader中已實(shí)現(xiàn)該方法邏輯)蒲跨,通過這個(gè)方法不僅能夠通過class文件實(shí)例化class對(duì)象译断,也可以通過其他方式實(shí)例化class對(duì)象,如通過網(wǎng)絡(luò)接收一個(gè)類的字節(jié)碼或悲,然后轉(zhuǎn)換為byte字節(jié)流創(chuàng)建對(duì)應(yīng)的Class對(duì)象孙咪。
  • resolveClass(Class??? c) ,類解析階段的操作方法巡语,給Classloader鏈接一個(gè)類翎蹈。

注意:
如果直接調(diào)用defineClass()或findClass()方法生成類的Class對(duì)象,這個(gè)類的Class對(duì)象并沒有解析(也可以理解為鏈接階段男公,畢竟解析是鏈接的最后一步)荤堪,其解析操作需要等待初始化階段進(jìn)行。

重復(fù)類加載

Java中來自不同Jar包中的相同的類名(包名枢赔,類名)在加載時(shí)類加載器將按照Class Path中的順序加載澄阳,相同的類名僅僅會(huì)加載一次,順序排在前面的類會(huì)被加載踏拜,加載成功后寇荧,后面再遇到相同類會(huì)忽略。
因此执隧,最終所使用的類取決于ClassLoader對(duì)類的的選擇揩抡,即Maven往Class Path打包的順序。

類卸載

類加載后JVM內(nèi)存圖

JVM中的Class只有滿足以下三個(gè)條件镀琉,才能被GC回收峦嗤,也就是該Class被卸載(unload):

  1. 該類所有的實(shí)例都已經(jīng)被GC。
  2. 該類的java.lang.Class對(duì)象沒有在任何地方被引用屋摔。
  3. 加載該類的ClassLoader實(shí)例已經(jīng)被GC烁设。

所以

  • 由Java虛擬機(jī)自帶的類加載器(啟動(dòng)類加載器、擴(kuò)展類加載器、應(yīng)用程序類加載器)所加載的類装黑,在虛擬機(jī)的生命周期中副瀑,始終不會(huì)被卸載。因?yàn)镴ava虛擬機(jī)本身會(huì)始終引用這些類加載器恋谭,而這些類加載器則會(huì)始終引用它們所加載的類的Class對(duì)象糠睡,因此這些Class對(duì)象始終是可觸及的。
  • 由用戶自定義的類加載器加載的類是可以被卸載的疚颊,如下圖使用自定義加載器加載的類和對(duì)象狈孔,只要左側(cè)三個(gè)引用變量置為null,類加載器生命周期結(jié)束材义、加載的Class對(duì)象生命周期結(jié)束均抽、Sample對(duì)象生命周期結(jié)束,方法區(qū)的類信息也會(huì)被卸載其掂。

是否同個(gè)類

在JVM中表示兩個(gè)class對(duì)象是否為同一個(gè)類對(duì)象存在兩個(gè)必要條件

  1. 類的完整類名必須一致油挥,包括包名。
  2. 加載這個(gè)類的ClassLoader(指ClassLoader實(shí)例對(duì)象)必須相同款熬。

獲取Class對(duì)象的三種方式

Class類是反射機(jī)制的起源喘漏,我們得到Class類對(duì)象有3種方法:
第一種:通過類名獲得
  Class<?> class = ClassName.class;
第二種:通過類名全路徑獲得:
  Class<?> class = Class.forName("類名全路徑");
第三種:通過實(shí)例對(duì)象獲得:
  Class<?> class = object.getClass();

Class.forName()和ClassLoader.loadClass()區(qū)別

  • Class.forName():將類的.class文件加載到j(luò)vm中之外,還會(huì)對(duì)類進(jìn)行解釋华烟,執(zhí)行類中的static塊;
  • ClassLoader.loadClass():只干一件事情持灰,就是將.class文件加載到j(luò)vm中盔夜,不會(huì)執(zhí)行static中的內(nèi)容,只有在newInstance才會(huì)去執(zhí)行static塊。

注:
Class.forName(name, initialize, loader)帶參函數(shù)也可控制是否加載static塊堤魁。并且只有調(diào)用了newInstance()方法采用調(diào)用構(gòu)造函數(shù)喂链,創(chuàng)建類的對(duì)象 。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末妥泉,一起剝皮案震驚了整個(gè)濱河市椭微,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌盲链,老刑警劉巖蝇率,帶你破解...
    沈念sama閱讀 218,755評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異刽沾,居然都是意外死亡本慕,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門侧漓,熙熙樓的掌柜王于貴愁眉苦臉地迎上來锅尘,“玉大人,你說我怎么就攤上這事布蔗√傥ィ” “怎么了浪腐?”我有些...
    開封第一講書人閱讀 165,138評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長顿乒。 經(jīng)常有香客問我议街,道長,這世上最難降的妖魔是什么淆游? 我笑而不...
    開封第一講書人閱讀 58,791評(píng)論 1 295
  • 正文 為了忘掉前任傍睹,我火速辦了婚禮,結(jié)果婚禮上犹菱,老公的妹妹穿的比我還像新娘拾稳。我一直安慰自己,他們只是感情好腊脱,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,794評(píng)論 6 392
  • 文/花漫 我一把揭開白布访得。 她就那樣靜靜地躺著,像睡著了一般陕凹。 火紅的嫁衣襯著肌膚如雪悍抑。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,631評(píng)論 1 305
  • 那天杜耙,我揣著相機(jī)與錄音搜骡,去河邊找鬼。 笑死佑女,一個(gè)胖子當(dāng)著我的面吹牛记靡,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播团驱,決...
    沈念sama閱讀 40,362評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼摸吠,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼!你這毒婦竟也來了嚎花?” 一聲冷哼從身側(cè)響起寸痢,我...
    開封第一講書人閱讀 39,264評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎紊选,沒想到半個(gè)月后啼止,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,724評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡兵罢,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年族壳,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片趣些。...
    茶點(diǎn)故事閱讀 40,040評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡仿荆,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情拢操,我是刑警寧澤锦亦,帶...
    沈念sama閱讀 35,742評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站令境,受9級(jí)特大地震影響杠园,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜舔庶,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,364評(píng)論 3 330
  • 文/蒙蒙 一抛蚁、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧惕橙,春花似錦瞧甩、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至彬坏,卻和暖如春朦促,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背栓始。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評(píng)論 1 270
  • 我被黑心中介騙來泰國打工务冕, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人幻赚。 一個(gè)月前我還...
    沈念sama閱讀 48,247評(píng)論 3 371
  • 正文 我出身青樓禀忆,卻偏偏與公主長得像,于是被迫代替她去往敵國和親坯屿。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,979評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容