1贸营、類加載的時機
類從被加載到虛擬機內(nèi)存中開始,到卸載出內(nèi)存為止,它的整個生命周期包括:加載(Loading)侍筛、驗證(Verification)、準(zhǔn)備(Preparation)撒穷、解析(Resolution)匣椰、初始化(Initialization)、使用(Using)和卸載(Unloading)端礼。
? ??
2禽笑、有且只有以下五種情況必須立即對類進行(初始化)
①遇到new、getstatic蛤奥、pusstatic或invokestatic這4條字節(jié)碼zhiling時佳镜,如果類沒有進行過初始化,則需要先出發(fā)其初始化凡桥。生成這4條指令的最常見的java代碼場景是:使用new關(guān)鍵字實例化對象的時候蟀伸、讀取或設(shè)置一個類的靜態(tài)字段(被final修飾、已在編譯期把結(jié)果放入常量池的靜態(tài)字段除外)的時候缅刽,以及調(diào)用一個類的靜態(tài)方法的時候啊掏。
②使用java.lang.reflect包的方法對類進行反射調(diào)用的時候,如果類沒有進行過初始化拷恨,則需要先出發(fā)其初始化脖律。
③當(dāng)初始化一個類的時候,如果發(fā)現(xiàn)其父類還沒有進行過初始化腕侄,則需要先觸發(fā)其父類的初始化小泉。
④當(dāng)虛擬機啟動時,用戶需呀執(zhí)行一個要執(zhí)行的主類(包含main()方法的那個類)冕杠,虛擬機會先初始化這個主類微姊。
⑤當(dāng)使用jdk1.7的動態(tài)語言支持時,如果一個java.lang.invoke.MethodHandle實例最后的解析結(jié)果REF_getStatic分预、REF_putStatic兢交、REF_invokeStatic的方法句柄,并且這個方法句柄所對應(yīng)的類沒有進行過初始化笼痹,則需要先觸發(fā)其初始化配喳。
3酪穿、以上5種引用被稱為主動引用,以下是被動引用的例子
? ??
4晴裹、加載被济。虛擬機完成以下三件事
通過一個類的全限定名來獲取定義此類的二進制字節(jié)流
將這個字節(jié)流所代表的靜態(tài)存儲結(jié)構(gòu)轉(zhuǎn)化為方法區(qū)的運行時數(shù)據(jù)結(jié)構(gòu)
在內(nèi)存中生成一個代表這個類的java.lang.Class對象,最為方法區(qū)這個類的各種數(shù)據(jù)的訪問入口涧团。
說明:加載二進制字節(jié)流的來源
從zip包中獲取只磷,最終成為日后jar、ear泌绣、war格式的基礎(chǔ)
從網(wǎng)絡(luò)中獲取钮追。典型應(yīng)用applet
運行時計算生成。使用場景動態(tài)代理技術(shù)阿迈,java.lang.reflect.Proxy
由其他文件生成元媚,典型場景是jsp應(yīng)用,即由jsp文件生成對應(yīng)的class類
從數(shù)據(jù)庫中讀取苗沧,中間件服務(wù)器
數(shù)組類加載說明:數(shù)組類本身不通過類加載器創(chuàng)建惠毁,她由java虛擬機直接創(chuàng)建的。單數(shù)組類與類加載器仍然有很密切的關(guān)系崎页,因為數(shù)組類的元素類型(elementType,指的是數(shù)組去掉所有維度的類型)最終是要靠類加載器去創(chuàng)建腰埂,一個數(shù)組類飒焦。
5、驗證:是連接階段的第一步屿笼,這一階段的目的是為了確保class文件的字節(jié)流中包含的信息符合當(dāng)前虛擬機的要求牺荠,并且不會危害虛擬機自身的安全。
文件格式驗證:字節(jié)流是否符合Class文件格式的規(guī)范
元數(shù)據(jù)驗證:對字節(jié)碼描述的信息進行語義分析
字節(jié)碼驗證:通過數(shù)據(jù)流和控制流分析驴一,確定程序語義是合法的休雌、符合邏輯的
符號引用驗證:虛擬機將符號轉(zhuǎn)化為直接引用的時候
6、準(zhǔn)備:正式為類變量分配內(nèi)存并設(shè)置類變量初始值的階段肝断,這些變量所使用的內(nèi)存都將在方法區(qū)中進行分配杈曲。這個階段進行內(nèi)存分配的僅包括類變量(被static修飾的變量),而不包括實例變量胸懈,實例變量將會在對象實例化時隨著對象一起分配在java堆中担扑。其次,這里所說的初始值“通常情況”下是數(shù)據(jù)類型的零值趣钱。public static int value=123涌献;準(zhǔn)備階段是0,程序被編譯后value=123首有⊙嗬“特殊情況”
:如果類字段的字段屬性變種ConstangVaule屬性枢劝,那在準(zhǔn)備階段變量value就會被初始化為ConstangValue屬性所指定的值。
7卜壕、解析:虛擬機將常量池內(nèi)的符號引用替換為直接引用的過程您旁。
????????類或接口的解析
????????字段解析
????????類方法解析
????????接口方法解析
8、初始化:執(zhí)行類構(gòu)造器方法的過程
① ????<clinit>()方法是由編譯器自動收集類中的所有類變量的賦值動作和靜態(tài)語句塊(static{}塊)中的語句合并產(chǎn)生的印叁,編譯器收集的順序是由語句在源文件中出現(xiàn)的順序所決定的被冒,靜態(tài)語句塊中只能訪問到定義在靜態(tài)語句塊之前的變量,定義在它之后的變量轮蜕,在前面的靜態(tài)語句塊可以賦值昨悼,但是不能訪問
②????<clinit>()方法與類的構(gòu)造函數(shù)不同,她不需要顯示地調(diào)用父類構(gòu)造器跃洛,虛擬機會保證在子類的<clinit>()方法執(zhí)行之前率触,父類的<clinit>()方法已經(jīng)執(zhí)行完畢。
③????由于父類的<clinit>()方法先執(zhí)行汇竭,也就意味著父類中定義的靜態(tài)語句塊要優(yōu)先于子類的變量賦值操作葱蝗。
? ??
④????<clinit>()方法對與類或接口來說并不是必須的,如果一個類中沒有靜態(tài)語句塊细燎,也沒有對變量的賦值操作两曼,那么編譯器可以不為這個類生成<clinit>()方法。
⑤????接口中不能使用靜態(tài)語句塊玻驻,單任然有變量初始化的賦值操作悼凑,因此接口與類一樣都會生成<clinit>()方法。但接口與類不同的是璧瞬,執(zhí)行接口的<clinit>()方法不需要先執(zhí)行父接口的<clinit>()方法户辫。只有單父接口中定義的變量使用時,符接口才會初始化嗤锉。另外渔欢,接口的實現(xiàn)類在初始化時也一樣不會執(zhí)行接口的<clinit>()方法。
⑥????虛擬機會保證一個類的<clinit>()方法在多線程環(huán)境中被正確地加鎖瘟忱、同步奥额,如果多個線程同時去初始化一個類,那么只會有一個線程去執(zhí)行這個類的<clinit>()方法酷誓,其他線程都需要阻塞等待披坏,知道活動線程執(zhí)行<clinit>()方法完畢。如果一個類的<clinit>()方法中有耗時很長的操作盐数,就可能造成多個進程阻塞棒拂,在實際應(yīng)用中這種阻塞往往是很隱蔽的。
9、類加載器
① 類與類加載器
類加載器雖然只用于顯示類的加載動作帚屉,但它在java程序中起到的作用卻遠(yuǎn)遠(yuǎn)不限于類加載階段谜诫。對于任意一個類,都需要由加載它的類加載器和這個類本身一同確立其在java虛擬機中的唯一性攻旦,每一個類加載器喻旷,都擁有一個獨立的類名稱空間,即比較兩個類是否相等牢屋,只有在這兩個類是由同一個類加載器加載的前提下才有意義且预,否則,及時這兩個類來源同一個Class文件烙无,唄同一個虛擬機加載锋谐,只要加載他們的類加載器不同,那這兩個類必定不相等截酷。
以上為false的結(jié)果涮拗,因為虛擬機中存在勒兩個ClassLoaderTest類,一個是由系統(tǒng)應(yīng)用程序類加載器加載的迂苛,另一個是由我們自定義的類加載器加載的三热,雖然都來自同一個Class文件,但依然是兩個獨立的類三幻,做對象所屬類型檢查是結(jié)果自然為false就漾。
② 雙親委派模型
從java虛擬機的角度來講,只存在兩種不同的類加載器:一種是啟動類加載器(bootstrap classloader)念搬,這個類加載器使用C++語言實現(xiàn)从藤,是虛擬機自身的一部分;另一種就是所有其他的類加載器锁蠕,這些類加載器都由java語言實現(xiàn),獨立于虛擬機外部懊蒸,并且圈都繼承自抽象類java.lang.ClassLoader荣倾。
????????①啟動類加載器(Bootstrap ClassLoader):負(fù)責(zé)將存放在\lib目錄中的,或者被-Xbootclasspath參數(shù)鎖指定的路徑中的骑丸,并且是虛擬機是被的類庫加載到虛擬機內(nèi)存中舌仍。啟動類加載器無法被java程序直接引用,用戶在編寫自定義類加載器時通危,如果需要把加載請求委派給引導(dǎo)類加載器铸豁,那直接使用null代替即可
????????②擴展類加載器(Extension ClassLoader):這個加載器由sun.misc.Launcher$ExtClassLoader實現(xiàn),它負(fù)責(zé)\lib\ext目錄中的菊碟,或者被java.ext.dirs系統(tǒng)變量所指定的路徑中的所有類庫节芥,開發(fā)者可以直接使用擴展類加載器。
????????③應(yīng)用程序類加載器(Application ClassLoader):這個類加載器由sun.misc.Launcher$AppClassLoader實現(xiàn)。由于這個類加載器時ClassLoader中的getSystemClassLoader()方法的返回值头镊,所以一邊稱他為系統(tǒng)類加載器蚣驼。它負(fù)責(zé)加載用戶類路徑(ClassPath)上所指定的類庫,開發(fā)者可以直接使用這個類加載器相艇,如果應(yīng)用程序中國沒有自定義過自己的類加載器颖杏,一邊情況下這個就是程序中默認(rèn)的類加載器。
雙親委派模型(Parents Delegation Model):要求除了頂層的啟動類加載器外坛芽,其余的類加載器都硬蛋有自己的父類加載器留储。這里類加載器之間的負(fù)責(zé)關(guān)系一邊不會一繼承的管事來實現(xiàn),而是都使用組合關(guān)系來復(fù)用父加載器的代碼咙轩。
雙親委派模型的工作過程:如果一個類加載器收到了類加載的請求获讳,它首先不會自己去嘗試加載這個類,而是把這個請求委派給父類加載器去完成臭墨,每一個層次的類加載器都是如此赔嚎,因此所有的加載請求最終都應(yīng)該傳送到頂層的啟動類加載器中,只有當(dāng)附加在前反饋自己無法完成這個加載請求時胧弛,自加載器才會嘗試自己去加載尤误。
破壞雙親委派模型
????????①jdk1.2上線后為了保證類加載機制復(fù)合雙親模式,并兼容之前的代碼在ClassLoader中新加了一個findClass()方法结缚。
????????②基礎(chǔ)類調(diào)用用戶的代碼损晤。如JNDI(需要調(diào)用獨立廠商實現(xiàn)并部署的應(yīng)用程序的ClassPath下的jndi接口提供者SPI,service provider interface)红竭,但啟動類加載器不可能認(rèn)識這些代碼尤勋,java開發(fā)團隊引入線程上下文類加載器(Thread Context ClassLoader)。java中所有涉及spi的加載動作基本上都采用這種方式茵宪。如JNDI,JDBC,JAXB,JBI
????????③用戶對程序動態(tài)性的追求導(dǎo)致的最冰。代碼如替換、模塊熱部署稀火。