JVM的類加載機(jī)制

類的生命周期

image.png

其中怜校,加載,驗(yàn)證注竿,準(zhǔn)備茄茁,初始化和卸載這5個(gè)階段的順序是確定的,類的加載過程必須按照這種順序開始巩割,而類的解析不一定裙顽,類的解析可能在初始化階段之后再開始,這是為了支持Java語言的動態(tài)綁定

Java的動態(tài)綁定和靜態(tài)綁定

在Java中宣谈,當(dāng)你調(diào)用一個(gè)方法時(shí)愈犹,可能會在編譯時(shí)期(compile time)解析(resolve),也可能實(shí)在運(yùn)行時(shí)期(runtime)解析闻丑,這全取決于到底是一個(gè)靜態(tài)方法(static method)還是一個(gè)虛方法(virtual method)漩怎。如果是在編譯時(shí)期解析,那么就稱之為靜態(tài)綁定(static binding)嗦嗡,如果方法的調(diào)用是在運(yùn)行時(shí)期解析勋锤,那就是動態(tài)綁定(dynamic binding)或者延遲綁定(late binding)。Java是一門面向?qū)ο蟮木幊陶Z言酸钦,優(yōu)勢就在于支持多態(tài)(Polymorphism)怪得。多態(tài)使得父類型的引用變量可以引用子類型的對象。如果調(diào)用子類型對象的一個(gè)虛方法(非private,final or static)卑硫,編譯器將無法找到真正需要調(diào)用的方法,因?yàn)樗赡苁嵌x在父類型中的方法蚕断,也可能是在子類型中被重寫(override)的方法欢伏,這種情形,只能在運(yùn)行時(shí)進(jìn)行解析亿乳,因?yàn)橹挥性谶\(yùn)行時(shí)期硝拧,才能明確具體的對象到底是什么。這也是我們俗稱的運(yùn)行時(shí)或動態(tài)綁定(runtime or dynamic binding)葛假。另一方面障陶,private static和final方法將在編譯時(shí)解析,因?yàn)榫幾g器知道它們不能被重寫聊训,所有可能的方法都被定義在了一個(gè)類中抱究,這些方法只能通過此類的引用變量進(jìn)行調(diào)用。這叫做靜態(tài)綁定或編譯時(shí)綁定(static or compile time binding)带斑。所有的private,static和final方法都通過靜態(tài)綁定進(jìn)行解析鼓寺。這兩個(gè)概念的關(guān)系勋拟,與“方法重載”(overloading,靜態(tài)綁定)和“方法重寫”(overriding妈候,動態(tài)綁定)類似敢靡。動態(tài)綁定只有在重寫可能存在時(shí)才會用到,而重載的方法在編譯時(shí)期即可確定(這是因?yàn)樗鼈兛偸嵌x在同一個(gè)類里面)

總而言之苦银,其區(qū)別如下:

①靜態(tài)綁定在編譯時(shí)期啸胧,動態(tài)綁定在運(yùn)行時(shí)期。

②靜態(tài)綁定只用到類型信息幔虏,方法的解析根據(jù)引用變量的類型決定纺念,而動態(tài)綁定則根據(jù)實(shí)際引用的的對象決定

③在java中,private static 和 final 方法都是靜態(tài)綁定所计,只有虛方法才是動態(tài)綁定

④多態(tài)是通過動態(tài)綁定實(shí)現(xiàn)的柠辞。

類在何時(shí)會被初始化

  • 遇到new(創(chuàng)建對象), getstatic(讀取靜態(tài)字段)主胧,putstatic(設(shè)置靜態(tài)字段)叭首,invokestatic(執(zhí)行靜態(tài)方法)的指令的時(shí)候,類才會被初始化
  • 使用Java類中的反射踪栋,對類進(jìn)行反射調(diào)用
  • 當(dāng)初始化子類的時(shí)候發(fā)現(xiàn)父類還沒被初始化焙格,這時(shí)候會先初始化父類,然后再初始化子類
  • JVM啟動的時(shí)候Java程序的入口main方法所在的類
  • Java的動態(tài)語言支持:MethodHandler中有涉及到static字段的讀取或者設(shè)置夷都,static方法的調(diào)用眷唉,這些也需要類進(jìn)行初始化,JDK1.7中囤官,Java的方法也能過當(dāng)作方法的參數(shù)進(jìn)行傳遞冬阳,類似于c語言中的函數(shù)指針
  • 只有以上這幾種情況才可能主動觸發(fā)類的初始化,其他的情況党饮,類的初始化都是被動的
    例如:
public class SubClass extends SuperClass {

    static {
        System.out.println("I am sub class!");
    }
}

public class SuperClass {

    public static int value = 9;

    static {
        System.out.println("I am super class!");
    }
}

public class DemoMain {

    public static void main(String[] args) {
        System.out.println("test:" + SubClass.value);
    }

}

SuperClass中定義了一個(gè)static字段value肝陪,在程序運(yùn)行的時(shí)候通過子類區(qū)調(diào)用value,這時(shí)候只會對SuperClass進(jìn)行初始化刑顺,而不會對SubClass初始化氯窍,運(yùn)行結(jié)果如下:

I am super class!
test:9

所以對于靜態(tài)字段的引用,雖然是SubClass.value蹲堂,但是由于value是static的狼讨,并且是在SuperClass中聲明的,所以只會初始化SuperClass柒竞,當(dāng)使用SubClass.value來訪問value的時(shí)候政供,這時(shí)候是跟SubClass無關(guān)的,父類中的static字段或者static方法是可以被子類覆蓋的,如果子類中有相同的聲明鲫骗,那么對于這個(gè)子類就會默認(rèn)覆蓋掉父類中對應(yīng)的聲明犬耻,如果子類中沒有相同的聲明,那么就直接繼承自父類中的相關(guān)方法或者變量执泰,但是static方法不具有多態(tài)枕磁,因?yàn)槎鄳B(tài)是對于一個(gè)對象來說才有多種形態(tài),對于類來說沒有多態(tài)的概念术吝,而static方法就是相對于類而言的计济,因?yàn)閟tatic方法不具備多態(tài)特性,舉個(gè)例子:

A是父類排苍,B繼承自A

A b = new B();
b.callMethod();
這就是b對象的多態(tài)沦寂,A中聲明了callMethod方法,B中也可能也聲明了callMethod方法淘衙,那么在b對象調(diào)用callMethod方法的時(shí)候執(zhí)行的是B對象中重寫的callMethod方法传藏,這是在運(yùn)行期間才知道callMethod方法要調(diào)用的是B的方法,多態(tài)就是Java中的動態(tài)綁定彤守,在運(yùn)行期間才能確定具體調(diào)用的是哪一個(gè)方法

類加載過程

  • 通過一個(gè)類的全限定名來獲取定義此類的二進(jìn)制流毯侦,這句話的意思是:java文件經(jīng)過編譯后會生成class文件,class文件只是字節(jié)碼具垫,這個(gè)機(jī)器無法識別侈离,所以在類加載的時(shí)候需要先把class字節(jié)碼轉(zhuǎn)換成二進(jìn)制的格式,這樣機(jī)器才能識別
  • 將前面由class字節(jié)碼轉(zhuǎn)換過來的二進(jìn)制字節(jié)流代表的靜態(tài)存儲結(jié)構(gòu)轉(zhuǎn)化成方法區(qū)的運(yùn)行時(shí)數(shù)據(jù)結(jié)構(gòu)筝蚕,這里我的理解是卦碾,一個(gè)類的信息(類全限定名,類中的方法起宽,字段等)是存儲在方法區(qū)中的洲胖,這里就是把二進(jìn)制字節(jié)流識別里面的一些類信息結(jié)構(gòu),然后存儲到指定的方法去(這里其實(shí)說的很模糊)
  • 在內(nèi)存中生成一個(gè)Class對象來作為方法區(qū)這個(gè)類的訪問入口

在類的加載過程中坯沪,數(shù)組的加載不需要類加載器來加載宾濒,而是java虛擬機(jī)直接創(chuàng)建的,但是數(shù)據(jù)中的元素如果還是一個(gè)類對象屏箍,那么這個(gè)元素對應(yīng)的類也需要類加載器來加載;在生成Class對象的時(shí)候需要注意橘忱,Class對象其實(shí)是一個(gè)對象赴魁,但是它是存在方法區(qū)中的,正常情況下Class對象的引用存在JVM棧中钝诚,而對象實(shí)際分配的內(nèi)存空間是在堆內(nèi)存中

類的驗(yàn)證

  • 類的驗(yàn)證是為了確保class文件的字節(jié)流中的信息符合當(dāng)前虛擬機(jī)的要求颖御,并不會導(dǎo)致虛擬機(jī)出現(xiàn)崩潰,如果驗(yàn)證失敗了那么JVM會拋出一個(gè)java.lang.VerifyError異常或者其子類潘拱,我曾經(jīng)就遇到過這么一個(gè)異常疹鳄,比如在Android4.0的系統(tǒng)上ReflectiveOperationException這個(gè)異常類,但是在Android6.0上就有這個(gè)類芦岂,那么在6.0下面編譯成功的apk放到4.0系統(tǒng)上運(yùn)行瘪弓,如果是如下代碼就會在apk安裝啟動的時(shí)候就會報(bào)這個(gè)VerifyError異常:
try {
            // ...
        } catch (ReflectiveOperationException e) {
            
        }

但是如果修改成下面這種,在程序不運(yùn)行到這里的時(shí)候不會崩潰禽最,一切正常腺怯,一旦程序運(yùn)行到這里,那么就會報(bào)NoClassDefFoundError異常:

System.out.println("" + ReflectiveOperationException.class);

這個(gè)說明在程序運(yùn)行到這里的時(shí)候JVM才去加載ReflectiveOperationException這個(gè)類川无,但是加載失敗呛占,而前面一個(gè)是程序一啟動運(yùn)行的時(shí)候,JVM會去加載引用ReflectiveOperationException的這個(gè)類懦趋,并且加載成功了晾虑,然后就會對引用了ReflectiveOperationException的這個(gè)類進(jìn)行驗(yàn)證,說明JVM在解析try catch語句的時(shí)候是有所不同的

類驗(yàn)證主要做了以下幾種驗(yàn)證:

  • 文件格式驗(yàn)證仅叫,例如魔數(shù)是否正確帜篇, 常量池中的常量是否有不被支持的常量類型(檢查常量tag標(biāo)志)等
  • 元數(shù)據(jù),這個(gè)其實(shí)就是驗(yàn)證語法的合法性惑芭,例如abstract修飾的父類方法是否被子類實(shí)現(xiàn)等
  • 字節(jié)碼驗(yàn)證坠狡,這個(gè)很復(fù)雜,暫時(shí)不了解遂跟,也不想了解
  • 符號引用的驗(yàn)證逃沿,符號中的字符串描述的全限定名是否能找到對應(yīng)的類,類中是否能找到指定的方法字段幻锁,符號引用中的類凯亮,字段,方法的訪問性(private哄尔,protected假消,public)是否能夠被成功訪問,如果不行就會拋出NoSuchFieldError岭接,NoSuchMethodError富拗,IllegalAccessError

類的準(zhǔn)備階段

  • 準(zhǔn)備階段是為類變量分配內(nèi)存并設(shè)置初始值,這些變量所使用的內(nèi)存都是在方法區(qū)中分配的鸣戴,但是要注意啃沪,準(zhǔn)備階段所分配內(nèi)存的變量都是被static修飾的,如果是實(shí)例變量或者其他局部變量窄锅,那會隨著對象的實(shí)例化在堆內(nèi)存中分配创千,假設(shè):
public static final int value = 9;

那么在類準(zhǔn)備階段后,由于value變量被static修飾,那么value的初始值會為0追驴,所以value就可以被其他類訪問械哟,當(dāng)類初始化之后,執(zhí)行了<clinit> ()方法殿雪,value的值才是9

類的解析

  • 解析就是把class文件中的符號引用轉(zhuǎn)換成直接引用暇咆,符號引用就是類的全限定名,可以是任意的字面量冠摄,引用的目標(biāo)不一定已經(jīng)加載到內(nèi)存中糯崎,而直接引用就是直接指向目標(biāo)的指針,引用對象一定需要已經(jīng)被加載到內(nèi)存中河泳;Java中的多態(tài)(動態(tài)綁定)其實(shí)就是跟類的解析有關(guān)沃呢,類的解析可能發(fā)生在程序運(yùn)行期間(類初始化之后),因?yàn)閷τ诙鄳B(tài)來說在類的加載拆挥,驗(yàn)證薄霜,準(zhǔn)備過程中并不知道實(shí)際要調(diào)用哪一個(gè)對象的方法砚婆,只有在執(zhí)行代碼的時(shí)候才知道實(shí)際需要執(zhí)行哪一個(gè)對象的方法

類初始化

  • 類初始化是類加載過程的最后一步了私植,初始化其實(shí)就是執(zhí)行構(gòu)造器的過程舷暮,構(gòu)造器是JVM自動生成的扒披,它是去自動搜集類的變量,靜態(tài)代碼塊中的語句合并產(chǎn)生的
  • <clinit>()和類的構(gòu)造函數(shù)不同受裹,JVM會保證子類執(zhí)行<clinit>()方法之前會先執(zhí)行父類的<clinit>()方法宿百,不需要想構(gòu)造函數(shù)一樣需要顯式的調(diào)用父類的構(gòu)造函數(shù)壁公,所以O(shè)bject的<clinit>()一定是最先執(zhí)行的
  • <clinit>()不是必須的洲拇,如果類中沒有對變量進(jìn)行賦值操作奈揍,也沒有靜態(tài)代碼塊,那么就沒有<clinit>()方法
  • 虛擬機(jī)會保證<clinit>()方法在多線程的環(huán)境下同步執(zhí)行赋续,所以如果多線程同時(shí)去初始化一個(gè)類男翰,那么同一個(gè)時(shí)刻只有一個(gè)線程去執(zhí)行<clinit>()方法,其他線程都會等待纽乱,如果在一個(gè)類<clinit>()方法中有耗時(shí)人物蛾绎,可能造成多線程阻塞,例如在靜態(tài)代碼塊中執(zhí)行耗時(shí)操作

類加載器

  • 類加載器(ClassLoader)注意一點(diǎn)鸦列,比較兩個(gè)類是否“相等”的前提必須是這兩個(gè)類必須是同一個(gè)類加載加載的才有意義租冠,如果A類是由ClassLoader1加載,同時(shí)A類也由ClassLoader2加載了一次薯嗤,那么ClassLoader1加載的A和ClassLoader2加載的A不屬于同一個(gè)類肺稀,雖然都來自同一個(gè)Class文件,但是由于是不同的類加載器加載的所以依然是兩個(gè)獨(dú)立的類
  • 雙親委派模型应民,雙親委派模型的工作過程是如果一個(gè)類加載器收到了類加載的請求,它首先不會自己去嘗試加載這個(gè)類,而是把這個(gè)請求委派給父類的加載器去完成诲锹,每一個(gè)層次的類加載器都是如此繁仁,因此所以類加載請求最終都會傳送到頂層的啟動類加載器中,只有當(dāng)父類加載器反饋無法完成這個(gè)加載請求的時(shí)候归园,子類加載器才會嘗試自己去加載黄虱,使用雙親委派模型來進(jìn)行類加載的一個(gè)好處就是確保類加載的唯一性,例如Object對象被啟動類加載器加載過了庸诱,那么只要是java.lang.Object這個(gè)類全限定名來加載的都會去頂層的類加載器中找到已經(jīng)加載成功的Object對象捻浦,確保了Object的唯一性,避免不同的ClassLoader多次加載同一個(gè)類全限定名的類
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末桥爽,一起剝皮案震驚了整個(gè)濱河市朱灿,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌钠四,老刑警劉巖盗扒,帶你破解...
    沈念sama閱讀 217,277評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異缀去,居然都是意外死亡侣灶,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評論 3 393
  • 文/潘曉璐 我一進(jìn)店門缕碎,熙熙樓的掌柜王于貴愁眉苦臉地迎上來褥影,“玉大人,你說我怎么就攤上這事咏雌》苍酰” “怎么了?”我有些...
    開封第一講書人閱讀 163,624評論 0 353
  • 文/不壞的土叔 我叫張陵处嫌,是天一觀的道長栅贴。 經(jīng)常有香客問我,道長熏迹,這世上最難降的妖魔是什么檐薯? 我笑而不...
    開封第一講書人閱讀 58,356評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮注暗,結(jié)果婚禮上坛缕,老公的妹妹穿的比我還像新娘。我一直安慰自己捆昏,他們只是感情好赚楚,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,402評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著骗卜,像睡著了一般宠页。 火紅的嫁衣襯著肌膚如雪左胞。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,292評論 1 301
  • 那天举户,我揣著相機(jī)與錄音烤宙,去河邊找鬼。 笑死俭嘁,一個(gè)胖子當(dāng)著我的面吹牛躺枕,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播供填,決...
    沈念sama閱讀 40,135評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼拐云,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了近她?” 一聲冷哼從身側(cè)響起叉瘩,我...
    開封第一講書人閱讀 38,992評論 0 275
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎泄私,沒想到半個(gè)月后房揭,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,429評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡晌端,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,636評論 3 334
  • 正文 我和宋清朗相戀三年捅暴,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片咧纠。...
    茶點(diǎn)故事閱讀 39,785評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡蓬痒,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出漆羔,到底是詐尸還是另有隱情梧奢,我是刑警寧澤,帶...
    沈念sama閱讀 35,492評論 5 345
  • 正文 年R本政府宣布演痒,位于F島的核電站亲轨,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏鸟顺。R本人自食惡果不足惜惦蚊,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,092評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望讯嫂。 院中可真熱鬧蹦锋,春花似錦、人聲如沸欧芽。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽千扔。三九已至憎妙,卻和暖如春库正,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背尚氛。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評論 1 269
  • 我被黑心中介騙來泰國打工诀诊, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人阅嘶。 一個(gè)月前我還...
    沈念sama閱讀 47,891評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像载迄,于是被迫代替她去往敵國和親讯柔。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,713評論 2 354

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