類的加載步驟
JVM加載類的過程大致分為三步,裝載(Load)唠亚,連接(Link)区丑,初始化(Initialize)
-
裝載:加載類的二進(jìn)制文件(將.class加載如內(nèi)存)
- 通過一個(gè)類的全限定名來獲取定義此類的二進(jìn)制字節(jié)流。
- 將這個(gè)字節(jié)流所代表的靜態(tài)存儲(chǔ)結(jié)構(gòu)轉(zhuǎn)化為方法區(qū)的運(yùn)行時(shí)數(shù)據(jù)結(jié)構(gòu)橱野。
- 在java堆中生成一個(gè)代表這個(gè)類的java.lang.Class對(duì)象朽缴,做為方法區(qū)這些數(shù)據(jù)的訪問入口。
- 加載階段完成之后二進(jìn)制字節(jié)流就按照虛擬機(jī)所需的格式存儲(chǔ)在方區(qū)去中水援。
-
驗(yàn)證:確保唄加載類的正確性(驗(yàn)證.class的正確性)
- 【文件格式驗(yàn)證】驗(yàn)證字節(jié)流是否符合Class文件格式的規(guī)范密强,并且能被當(dāng)前版本的虛擬機(jī)處理茅郎。
- 【元數(shù)據(jù)驗(yàn)證】對(duì)字節(jié)碼描述的信息進(jìn)行語義分析,以確保其描述的信息符合java語言規(guī)范的要求或渤。
- 【字節(jié)碼驗(yàn)證】這個(gè)階段的主要工作是進(jìn)行數(shù)據(jù)流和控制流的分析系冗。任務(wù)是確保被驗(yàn)證類的方法在運(yùn)行時(shí)不會(huì)做出危害虛擬機(jī)安全的行為。
- 【符號(hào)引用驗(yàn)證】這一階段發(fā)生在虛擬機(jī)將符號(hào)引用轉(zhuǎn)換為直接引用的時(shí)候(解析階段)薪鹦,主要是對(duì)類自身以外的信息進(jìn)行匹配性的校驗(yàn)掌敬。目的是確保解析動(dòng)作能夠正常執(zhí)行。
-
準(zhǔn)備:為類的靜態(tài)變量分配內(nèi)存并初始化
- 這些內(nèi)存都將在方法區(qū)中進(jìn)行分配
- 這里的變量?jī)H包括類標(biāo)量不包括實(shí)例變量
-
解析:將類的符號(hào)引用變?yōu)橹苯右?/p>
- 【符號(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)的句柄离斩。直接飲用是與內(nèi)存布局相關(guān)的银舱。
初始化:為類的靜態(tài)變量賦予正確的值
類的加載時(shí)機(jī)
類的加載時(shí)機(jī)有一下六種:
- 創(chuàng)建類的實(shí)例的時(shí)候,也就是使用new創(chuàng)建對(duì)象的時(shí)候
- 訪問某個(gè)類或接口的靜態(tài)變量的時(shí)候跛梗,或者對(duì)該靜態(tài)變量賦值的時(shí)候
- 調(diào)用類的靜態(tài)方法的時(shí)候
- 反射
- 初始化類的子類的時(shí)候(會(huì)首先初始化子類的父類寻馏,接口加載時(shí)則無需加載所有父接口)
- JVM啟動(dòng)時(shí)的啟動(dòng)類
- 運(yùn)行時(shí)計(jì)算生成,最典型的是動(dòng)態(tài)代理技術(shù)
注意:虛擬機(jī)規(guī)范并沒有指明二進(jìn)制字節(jié)流要從一個(gè)Class文件獲取核偿,或者說根本沒有指明從哪里獲取诚欠、怎樣獲取。這種開放使得Java在很多領(lǐng)域得到充分運(yùn)用漾岳,例如:
從ZIP包中讀取轰绵,這很常見,成為JAR尼荆,EAR左腔,WAR格式的基礎(chǔ)
從網(wǎng)絡(luò)中獲取,最典型的應(yīng)用就是Applet
運(yùn)行時(shí)計(jì)算生成捅儒,最典型的是動(dòng)態(tài)代理技術(shù)液样,在java.lang.reflect.Proxy中,就是用了ProxyGenerator.generateProxyClass來為特定接口生成形式為“*$Proxy”的代理類的二進(jìn)制字節(jié)流
有其他文件生成巧还,最典型的JSP應(yīng)用鞭莽,由JSP文件生成對(duì)應(yīng)的Class類
加載器-類型
JVM默認(rèn)有三種類的加載器
- 【啟動(dòng)類加載器】使用C++代碼實(shí)現(xiàn)(不是Java類,因此它不需要被別人加載)麸祷,嵌套在Java虛擬機(jī)內(nèi)核里面澎怒,也就是JVM啟動(dòng)的時(shí)候Bootstrap就已經(jīng)啟動(dòng),例如java.lang等包下的類
- 【擴(kuò)展類加載器】負(fù)責(zé)載入標(biāo)準(zhǔn)擴(kuò)展目錄中的類阶牍,例如Sun的JVM的擴(kuò)展目錄是/jdk/jre/lib/ext喷面。
- 【系統(tǒng)類加載器】默認(rèn)的類加載器星瘾,搜索環(huán)境變量CLASSPATH中指明的路徑。
類的加載其他注意事項(xiàng):
【自定義類的加載器】
- 默認(rèn)很多情況下應(yīng)用程序會(huì)根據(jù)自身需求定義自己的類的加載器乖酬,如tomcat根據(jù)j2ee標(biāo)準(zhǔn)自行實(shí)現(xiàn)ClassLoader死相。
【類的卸載】
- 就是Class文件的垃圾回收,系統(tǒng)自帶的類的加載器加載的類不會(huì)被回收咬像。
【命名空間】
- 類使用命名空間(類的包名+類的加載器)確定類的唯一性
- 同一命名空間下的類是相互可見的
- 子類命名空間包含父類的命名空間算撮,因此由子類加載器記載的類能“看到”由父類加載器加載的類
類的加載機(jī)制(雙親委派機(jī)制)
通俗來講就是某個(gè)特定的類加載器在接到加載類的請(qǐng)求時(shí),首先將加載任務(wù)委托給父類加載器县昂,依次遞歸肮柜,如果父類加載器可以完成類加載任務(wù),就成功返回倒彰;只有父類加載器無法完成此加載任務(wù)時(shí)审洞,才自己去加載。
為什么要讓父類加載器優(yōu)先去加載呢待讳?
試想如果子類加載器先加載芒澜,那么我們可以寫一些與java.lang包中基礎(chǔ)類同名的類,
然后再定義一個(gè)子類加載器创淡,這樣整個(gè)應(yīng)用使用的基礎(chǔ)類就都變成我們自己定義的類了痴晦。這樣就有很大的安全隱患!
所以自己編寫類加載器時(shí)琳彩,如果沒有特殊原因誊酌,一定要遵守類加載的雙親委派模型。