類的加載步驟
JVM加載類的過程大致分為三步,裝載(Load),連接(Link),初始化(Initialize)

-
裝載:加載類的二進制文件(將.class加載如內(nèi)存)
- 通過一個類的全限定名來獲取定義此類的二進制字節(jié)流。
- 將這個字節(jié)流所代表的靜態(tài)存儲結(jié)構(gòu)轉(zhuǎn)化為方法區(qū)的運行時數(shù)據(jù)結(jié)構(gòu)。
- 在java堆中生成一個代表這個類的java.lang.Class對象,做為方法區(qū)這些數(shù)據(jù)的訪問入口。
- 加載階段完成之后二進制字節(jié)流就按照虛擬機所需的格式存儲在方區(qū)去中。
-
驗證:確保唄加載類的正確性(驗證.class的正確性)
- 【文件格式驗證】驗證字節(jié)流是否符合Class文件格式的規(guī)范,并且能被當前版本的虛擬機處理。
- 【元數(shù)據(jù)驗證】對字節(jié)碼描述的信息進行語義分析,以確保其描述的信息符合java語言規(guī)范的要求。
- 【字節(jié)碼驗證】這個階段的主要工作是進行數(shù)據(jù)流和控制流的分析。任務是確保被驗證類的方法在運行時不會做出危害虛擬機安全的行為。
- 【符號引用驗證】這一階段發(fā)生在虛擬機將符號引用轉(zhuǎn)換為直接引用的時候(解析階段),主要是對類自身以外的信息進行匹配性的校驗。目的是確保解析動作能夠正常執(zhí)行。
-
準備:為類的靜態(tài)變量分配內(nèi)存并初始化
- 這些內(nèi)存都將在方法區(qū)中進行分配
- 這里的變量僅包括類標量不包括實例變量
-
解析:將類的符號引用變?yōu)橹苯右?/p>
- 【符號引用】符號引用以一組符號來描述所引用的目標,符號可以是任意形式的字面量,只要使用時能無歧義地定位到目標即可。符號引用與虛擬機實現(xiàn)的內(nèi)存布局無關,引用的目標并不一定已經(jīng)加載到內(nèi)存中。
- 【直接引用】直接引用可以是直接指向目標的指針,相對偏移量或是一個能間接定位到目標的句柄。直接飲用是與內(nèi)存布局相關的。
初始化:為類的靜態(tài)變量賦予正確的值
類的加載時機
類的加載時機有一下六種:
- 創(chuàng)建類的實例的時候,也就是使用new創(chuàng)建對象的時候
- 訪問某個類或接口的靜態(tài)變量的時候,或者對該靜態(tài)變量賦值的時候
- 調(diào)用類的靜態(tài)方法的時候
- 反射
- 初始化類的子類的時候(會首先初始化子類的父類,接口加載時則無需加載所有父接口)
- JVM啟動時的啟動類
- 運行時計算生成,最典型的是動態(tài)代理技術
注意:虛擬機規(guī)范并沒有指明二進制字節(jié)流要從一個Class文件獲取,或者說根本沒有指明從哪里獲取、怎樣獲取。這種開放使得Java在很多領域得到充分運用,例如:
從ZIP包中讀取,這很常見,成為JAR,EAR,WAR格式的基礎
從網(wǎng)絡中獲取,最典型的應用就是Applet
運行時計算生成,最典型的是動態(tài)代理技術,在java.lang.reflect.Proxy中,就是用了ProxyGenerator.generateProxyClass來為特定接口生成形式為“*$Proxy”的代理類的二進制字節(jié)流
有其他文件生成,最典型的JSP應用,由JSP文件生成對應的Class類
加載器-類型
JVM默認有三種類的加載器
- 【啟動類加載器】使用C++代碼實現(xiàn)(不是Java類,因此它不需要被別人加載),嵌套在Java虛擬機內(nèi)核里面,也就是JVM啟動的時候Bootstrap就已經(jīng)啟動,例如java.lang等包下的類
- 【擴展類加載器】負責載入標準擴展目錄中的類,例如Sun的JVM的擴展目錄是/jdk/jre/lib/ext。
- 【系統(tǒng)類加載器】默認的類加載器,搜索環(huán)境變量CLASSPATH中指明的路徑。
類的加載其他注意事項:
【自定義類的加載器】
- 默認很多情況下應用程序會根據(jù)自身需求定義自己的類的加載器,如tomcat根據(jù)j2ee標準自行實現(xiàn)ClassLoader。
【類的卸載】
- 就是Class文件的垃圾回收,系統(tǒng)自帶的類的加載器加載的類不會被回收。
【命名空間】
- 類使用命名空間(類的包名+類的加載器)確定類的唯一性
- 同一命名空間下的類是相互可見的
- 子類命名空間包含父類的命名空間,因此由子類加載器記載的類能“看到”由父類加載器加載的類
類的加載機制(雙親委派機制)
通俗來講就是某個特定的類加載器在接到加載類的請求時,首先將加載任務委托給父類加載器,依次遞歸,如果父類加載器可以完成類加載任務,就成功返回;只有父類加載器無法完成此加載任務時,才自己去加載。

為什么要讓父類加載器優(yōu)先去加載呢?
試想如果子類加載器先加載,那么我們可以寫一些與java.lang包中基礎類同名的類,
然后再定義一個子類加載器,這樣整個應用使用的基礎類就都變成我們自己定義的類了。這樣就有很大的安全隱患!
所以自己編寫類加載器時,如果沒有特殊原因,一定要遵守類加載的雙親委派模型。