1)類加載過程散劫,執(zhí)行哪些操作稚机?????類加載器有了解嗎?
2)什么是雙親委派模型获搏?工作過程以及使用它的好處赖条。
代碼編譯結(jié)果從本地轉(zhuǎn)換為字節(jié)碼,是存儲格式發(fā)展一小步常熙,編程語言發(fā)展一大步纬乍。
一、概述
1.1類加載:
1)虛擬機把描述類數(shù)據(jù)從class文件加載到內(nèi)存裸卫,2)對數(shù)據(jù)進(jìn)行校驗蕾额、轉(zhuǎn)換解析和初始化。3)形成可被虛擬機直接用的java類型過程
1.2 Java動態(tài)加載和動態(tài)連接
加載連接彼城,及初始化過程都在程序運行間完成,稍微增加性能開銷退个,但高度靈活募壕。如,面向接口程序语盈,可運行時再指定具體實現(xiàn)類
二舱馅、 類加載時機
類從加載到虛擬機內(nèi)存到卸出內(nèi)存,生命周期:
嚴(yán)格規(guī)定:只有五種情況必須立即刀荒,對類進(jìn)行“初始化”:
????1.new實例化對象時代嗤、讀取或設(shè)置類的靜態(tài)字段時,已調(diào)用類的靜態(tài)方法時
????2.用java.lang.reflect對類反射調(diào)用時缠借,沒有初始化干毅,先觸發(fā)其初始化
????3.初始化類時,先初始化父類泼返。
????4.虛擬機啟動時硝逢,指定執(zhí)行主類(main()方法那個類),先初始化绅喉;
????5.用Jdk1.7動態(tài)語言支持
接口初始化時渠鸽,不要求父接口全部初始化,真正使用到父接口時(如引用父接口中常量)才初始化柴罐。
被動引用:引用時不觸發(fā)初始化:如:
①子類引用父類靜態(tài)字段徽缚,不會導(dǎo)致子類初始化;②數(shù)組定義引用類革屠,不觸發(fā)類初始化
③常量在編譯階段會存入調(diào)用類的常量池中凿试,沒直接引用常量類排宰,不觸發(fā)定義常量類初始化
三、類加載過程
3.1 加載
“類加載”一個階段红省,切不可混淆额各。三個基本動作組成:
????1) 類型完全限定名,產(chǎn)生代表該類型的二進(jìn)制數(shù)據(jù)流(沒有指明哪里吧恃、怎樣獲取虾啦,非常開放的平臺了)
????2) 解析二進(jìn)制數(shù)據(jù)流為方法區(qū)運行時數(shù)據(jù)結(jié)構(gòu)
????3) 創(chuàng)建表示該類型的java.lang.Class實例,作為方法區(qū)這個類的各種數(shù)據(jù)訪問入口
二進(jìn)制數(shù)據(jù)流常見形式:
? ? 1)從zip包中讀取痕寓,成JAR傲醉、EAR、WAR格式基礎(chǔ)呻率;2)從網(wǎng)絡(luò)中獲取硬毕,如Applet;
? ??3)運行時計算生成,動態(tài)代理????4)文件生成礼仗,如JSP吐咳;
ps:非數(shù)組類加載:可用系統(tǒng)類加載器,或自定義
3.2 驗證
連接第一步元践,確保Class文件字節(jié)流中信息符合虛擬機要求韭脊,不危害虛擬機安全,避免惡意攻擊单旁,完成4個階段校驗工作:文件格式沪羔、元數(shù)據(jù)、字節(jié)碼象浑、符號引用蔫饰。
3.2.1 文件格式驗證
驗證字節(jié)流是否符合Class文件格式的規(guī)范,并且能被當(dāng)前版本的虛擬機處理愉豺。該驗證階段的主要目的是保證輸入的字節(jié)流能正確地解析并存儲于方法區(qū)之內(nèi)篓吁。這個階段驗證是基于二進(jìn)制字節(jié)流進(jìn)行的,只有通過這個階段的驗證后蚪拦,字節(jié)流才會進(jìn)入內(nèi)存的方法區(qū)進(jìn)行存儲越除,所以后面的3個階段的全部是基于方法區(qū)的存儲結(jié)構(gòu)進(jìn)行的,不會再直接操作字節(jié)流外盯。
3.2.2 元數(shù)據(jù)驗證
該階段對字節(jié)碼描述的信息進(jìn)行語義分析摘盆,以保證其描述的信息符合Java語言規(guī)范的要求,目的是保證不存在不符合Java語言規(guī)范的元數(shù)據(jù)信息饱苟。
3.2.3 字節(jié)碼驗證
該階段主要工作時進(jìn)行數(shù)據(jù)流和控制流分析孩擂,保證被校驗類的方法在運行時不會做出危害虛擬機安全的行為。例如箱熬,保證跳轉(zhuǎn)指令不會跳轉(zhuǎn)到方法體以外的字節(jié)碼指令上类垦、保證方法體中的類型轉(zhuǎn)換是有效的等等狈邑。
由于數(shù)據(jù)流校驗的高復(fù)雜性,耗時較大蚤认,所以JDK1.6之后米苹,在Javac中引入一項優(yōu)化方法(可以通過參數(shù)關(guān)閉):在方法體的Code屬性的屬性表中增加一項“StackMapTable”屬性,該屬性描述了方法體中所有基本塊開始時本地變量表和操作棧應(yīng)有的狀態(tài)砰琢,從而將字節(jié)碼驗證的類型推導(dǎo)轉(zhuǎn)變?yōu)轭愋蜋z查從而節(jié)省一些時間蘸嘶。
注意:如果一個方法體通過了字節(jié)碼驗證,也不能說明其一定是安全的陪汽,因為校驗程序邏輯無法做到絕對精確训唱。
3.2.4 符號引用驗證
最后一個階段的校驗發(fā)生在虛擬機將符號引用轉(zhuǎn)化為直接引用的時候,這個轉(zhuǎn)化動作將在連接的第三個階段——解析階段中發(fā)生挚冤。符號引用驗證的目的是確保解析動作能正常執(zhí)行况增。
驗證的內(nèi)容主要有:
? 符號引用中通過字符串描述的全限定名是否能找到對應(yīng)的類;
? 在指定類中是否存在符號方法的字段描述及簡單名稱所描述的方法和字段训挡;
? 符號引用中的類澳骤、字段和方法的訪問性(private、protected澜薄、public为肮、default)是否可被當(dāng)前類訪問。
3.3 準(zhǔn)備
正式為類變量? 分配內(nèi)存? 并? 設(shè)置類變量初始值? 的階段表悬,所用的內(nèi)存都將在方法區(qū)中進(jìn)行分配。(備注:這時候進(jìn)行內(nèi)存分配的僅包括類變量(被static修飾的變量)丧靡,而不包括實例變量蟆沫,實例變量將會在對象實例化時隨著對象一起分配在Java堆中)。
初始值通常是數(shù)據(jù)類型的零值:
????對于:public static int value = 123;温治,那么變量value在準(zhǔn)備階段過后的初始值為0而不是123饭庞,這時候尚未開始執(zhí)行任何java方法,把value賦值為123的動作將在初始化階段才會被執(zhí)行熬荆。
一些特殊情況:
????對于:public static final int value = 123;編譯時Javac將會為value生成ConstantValue屬性舟山,在準(zhǔn)備階段虛擬機就會根據(jù)ConstantValue的設(shè)置將value賦值為123。
3.4 解析
將常量池內(nèi)的符號引用替換為直接引用的過程卤恳。
那么符號引用與直接引用有什么關(guān)聯(lián)呢累盗?
3.4.1 看兩者的概念。
符號引用(Symbolic References):符號引用以一組符號來描述所引用的目標(biāo)突琳,符號可以是符合約定的任何形式的字面量若债,符號引用與虛擬機實現(xiàn)的內(nèi)存布局無關(guān),引用的目標(biāo)并不一定已經(jīng)加載到內(nèi)存中拆融。
直接引用(Direct References):直接引用可以是直接指向目標(biāo)的指針蠢琳、相對偏移量或是一個能間接定位到目標(biāo)的句柄啊终。直接引用與虛擬機實現(xiàn)的內(nèi)存布局相關(guān),引用的目標(biāo)必定已經(jīng)在內(nèi)存中存在傲须。
虛擬機規(guī)范沒有規(guī)定解析階段發(fā)生的具體時間蓝牲,虛擬機實現(xiàn)可以根據(jù)需要來判斷到底是在類被加載時解析還是等到一個符號引用將要被使用前才去解析。
3.4.2 對解析結(jié)果進(jìn)行緩存
同一符號引用進(jìn)行多次解析請求是很常見的泰讽,除invokedynamic指令以外例衍,虛擬機實現(xiàn)可以對第一次解析結(jié)果進(jìn)行緩存,來避免解析動作重復(fù)進(jìn)行菇绵。無論是否真正執(zhí)行了多次解析動作肄渗,虛擬機需要保證的是在同一個實體中,如果一個引用符號之前已經(jīng)被成功解析過咬最,那么后續(xù)的引用解析請求就應(yīng)當(dāng)一直成功翎嫡;同樣的,如果 第一次解析失敗永乌,那么其他指令對這個符號的解析請求也應(yīng)該收到相同的異常惑申。
3.4.3 解析動作的目標(biāo)
解析動作主要針對類或接口、字段翅雏、類方法圈驼、接口方法、方法類型望几、方法句柄和調(diào)用點限定符7類符號引用進(jìn)行绩脆。前面四種引用的解析過程,對于后面三種橄抹,與JDK1.7新增的動態(tài)語言支持息息相關(guān)靴迫,由于java語言是一門靜態(tài)類型語言,因此沒有介紹invokedynamic指令的語義之前楼誓,沒有辦法將他們和現(xiàn)在的java語言對應(yīng)上玉锌。
3.5 初始化
最后一步,真正開始執(zhí)行類中定義的java程序代碼(或者說是字節(jié)碼)疟羹。前面除了 加載階段用戶應(yīng)用程序可以通過自定義類加載器參與之外主守,其余動作完全由虛擬機主導(dǎo)和控制。
四榄融、類加載器
4.1 類與類加載器
任意類参淫,都需由加載它的類加載器和這個類本身一同確立其在Java虛擬機中的唯一性。如果兩個類來源于同一個Class文件愧杯,只要加載它們的類加載器不同黄刚,那么這兩個類就必定不相等。
4.2 類加載器介紹
虛擬機角度分:啟動類加載器Bootstrap ClassLoader(C++語言實現(xiàn)民效,虛擬機自身的一部分)和其他類加載器(Java語言實現(xiàn)憔维,獨立于虛擬機之外涛救,并且全都繼承自java.lang.ClassLoader類)
Java用到以下3種:(一般3種類加載器互相配合加載)
? ? (1)啟動類加載器Bootstrap ClassLoader
這個類加載器負(fù)責(zé)將存放在\lib目錄中的,或者被-Xbootclasspath參數(shù)所指定的路徑中的业扒,并且是虛擬機識別的(僅按照文件名識別检吆,如rt.jar,名字不符合的類庫即使放在lib目錄中也不會被加載)類庫加載到虛擬機內(nèi)存中程储。
? ? (2)擴展類加載器Extension ClassLoader
sun.misc.Launcher$ExtClassLoader實現(xiàn)蹭沛,加載\lib\ext目錄中,或者被java.ext.dirs系統(tǒng)變量所指定的路徑中的所有類庫章鲤,可直接使用擴展類加載器
? ? (3)應(yīng)用程序類加載器(Application ClassLoader):
由sun.misc.Launcher$AppClassLoader實現(xiàn)摊灭,是ClassLoader中的getSystemClassLoader()返回值,也稱它為系統(tǒng)類加載器败徊。加載用戶類路徑(ClassPath)指定類庫帚呼,可直接用,如果沒有自定義皱蹦,默認(rèn)用這個
4.3 雙親委派模型
除頂層啟動類加載器外煤杀,都有父類加載器。組合關(guān)系沪哺,不繼承沈自,復(fù)用父加載器代碼
工作過程:類加載請求,先委派給父加載器去完成(最終都傳頂層啟動類)辜妓,父無法完成枯途,子才自己去加載。好處:有優(yōu)先級籍滴,不強制性 約束模型酪夷,大部分遵循
4.4 破壞雙親委派模型
3次較大規(guī)囊熘穑“被破壞”
第一次:因為類加載器和抽象類java.lang.ClassLoader在JDK1.0就存在的捶索,而雙親委派模型在JDK1.2之后才被引入插掂,為兼容自定義類加載器灰瞻,妥協(xié):java.lang.ClassLoader中引入了一個findClass(),之前繼承java.lang.Classloader就是重寫loadClass()辅甥。1.2之后不覆蓋loadClass()酝润,把類加載邏輯寫到findClass()中,如loadClass()方法中如果父類加載失敗璃弄,調(diào)用自己findClass()方法完成加載要销,保證新加載器是符合雙親委派模型。
第二次:因為模型自身缺陷夏块,場景:基礎(chǔ)類加載器調(diào)用戶代碼疏咐,但不認(rèn)識代碼纤掸。引入“線程上下文類加載器(Thread Context ClassLoader)”。通過父類請求子浑塞。違背了雙親
第三次破壞:對程序動態(tài)性追求導(dǎo)致借跪。動態(tài)性指不重啟就能用:“代碼熱替換”、“模塊熱部署”等酌壕。如OSGi掏愁。每個程序模塊(OSGi中Bundle)都有自己的類加載器,更換Bundle卵牍,連同類加載器一起換果港,實現(xiàn)熱替換。不再是雙親糊昙,更加復(fù)雜網(wǎng)狀結(jié)構(gòu)