一,類加載的過程
1,加載
通過一個類的全限定名來獲取此類的二進制字節流,將這個字節流所代表的靜態存儲結構轉換成方法區的運行時數據結構,在內存中生成一個代表這個類的java.lang.Class對象,作爲方法區這個類的各種數據的訪問入口
注意:Class對象不一定放在堆裏,對於Hospot虛擬機,放在了方法區裏
2,驗證
驗證Class文件的文件流中包含的信息是否符合當前虛擬機的要求,並且不會危害虛擬機安全
①文件格式驗證
保證輸入的字節流能正確地解析並存儲於方法區內,格式符合要求。此階段驗證基於二進制字節流進行,此階段驗證完畢後,字節流纔會進入內存的方法區,後面的驗證階段都是基於內存方法區的存儲結構進行的,而不是基於字節流。
②元數據驗證
對字節碼描述的信息進行語義分析,確保符合Java語言規範
如驗證此類是否有父類.除了Object都要有父類
驗證此類的父類是否繼承了不允許被繼承的類(如final)
如果此類不是抽象類,是否實現了其父類或接口要求實現的方法
類中的字段方法是否與父類相矛盾,如覆蓋父類final方法,重載時返回值類型不同
。。。
③字節碼驗證
確保程序語義合法。檢查類的方法體。
保證方法體的類型轉換是有效的,如可以把一個子類對象賦值給父類數據類型,但是不可以反過來。
④符號引用驗證
發生在連接的第三個階段——解析時,即虛擬機將符號引用轉換成直接引用的時候。
3,準備
爲類的靜態變量設置內存並設置爲虛擬機默認初始值,一般爲零值,如static int a=100; a值在準備階段爲0。賦值爲100的指令是程序被編譯後,存放於類構造器方法之中,在初始化階段纔會執行。
但是對於final修飾的常量,在準備階段就會直接賦值。如final static b=100.在準備階段將b賦值爲100
4,解析
虛擬機將常量池內的符號引用替換成直接引用
5,初始化
以下四種屬於對類進行主動引用,會觸發初始化
①遇到new、putstatic、setstatic、invokestatic這四條字節碼指令時,如果沒有初始化類,就要先初始化。對應java代碼場景是:new 關鍵字實例化對象;設讀取或設置類的靜態字段;調用類的靜態方法
②通過反射執行上述情況
③初始化子類時,如果父類沒有初始化,先初始化父類
④作爲主類調用main方法,會初始化主類
對類進行被動引用不會觸發初始化
引用父類的靜態字段,只會引起父類的初始化,而不會引起子類的初始化。
定義類數組,不會引起類的初始化。
引用類的常量,不會引起類的初始化。
二,Class.forName(className) 實際上是調用Class.forName(className, true, this.getClass().getClassLoader())。注意第二個參數,是指Class被loading後是不是必須被初始化。
ClassLoader.loadClass(className)實際上調用的是ClassLoader.loadClass(name, false),第二個參數指出Class是否被link。
區別就出來了。Class.forName(className)裝載的class已經被初始化,而ClassLoader.loadClass(className)裝載的class還沒有被link。
forName支持數組類型,loadClass不支持數組
一般情況下,這兩個方法效果一樣,都能裝載Class。但如果程序依賴於Class是否被初始化,就必須用Class.forName(name)了。
只有執行cls.NewInstance()才能夠得到該類的一個實例。NewInstance在得到實例前會先初始化。