Loading 加載
按如下三步執行
1.通過類的全名產生對應類的二進制數據流。(注意,根據early load的原理,如果沒找到對應類文件,只有在類實際使用時才拋出錯誤.)
2.分析並將這些二進制數據流轉換為 方法區(JVM的架構:方法區、堆,棧,本地方法棧,pc寄存器)特定的數據結構(這些數據結構是實現有關的,不同JVM有不同實現)。
這裡處理了部分verification(對正確性的檢驗),比如 .class文件的magic number , 文件是否過長或者過短。確定是否有父類(除了Obecjt類)。
3.創建對應類的 java.lang.Class 實例。(有了對應的Class實例,並不意味著這個類已經完成了加載鏈接!!)
Linking 鏈接
鏈接的過程比加載過成復雜不少,這是實現java的動態性的重要一步!分為三部分:verification (檢測), preparation(准備) 和 resolution(解析)
1.verification:(注意到有一些verification已經在loading的過程中執行)
linking的resolve會把類中成員方法、成員變量、類和接口的符號引用替換為直接引用,而在這之前,需要檢測被引用的類型正確性和接入屬性是否正確(就是public ,private的的問題)諸如,檢查final class 沒有被繼承,檢查靜態變量的正確性等等。
2.preparation:
對類的成員變量分配空間。
雖然有初始值,但這個時候不會對他們進行初始化(因為這裡不會執行任何java代碼)。。具體如下:
所有primitive type都為 0 值。如float : 0f , int 0 , boolean 0 (注意boolean底層實現大多使用int),
引用類型則為 null
值得注意的是,JVM可能會在這個時期給一些有助於程序運行效率提高的數據結構分配空間。比如method table(類似與C++中的虛函數表,參見另一篇《Java:方法的虛分派(virtual dispatch)和方法表(method table)》 )。
3.Resolution
為類、接口、方法、成員變量的符號引用定位直接引用(符號引用先到constant pool中尋找符號,再找先應的類型,無疑會耗費更多時間),完成內存結構的布局。
注意,這一步也是可選的。可以在符號應用第一次被使用時完成,即所謂的 late resolution .
但是,對用戶而言,這一步永遠是late resolution的:即即使運行時會early resolution , 但程序不會顯示的在第一次判斷出錯誤時拋出錯誤,而會在對應的類第一次active use的時候拋出錯誤!
另外,這一步與之後的類初始化是不沖突的,並非一定要所有的resolution結束以後才執行類的初始化。不同的JVM實現不同。
詳情見另一篇《Java類初始化的時機(主動調用和被動使用的區別) 》
Initialization 初始化類
我們接觸最多的是對對象的初始化,但類也是有初始化的。
相比對象初始化(參見《Java類的實例化探究》),類的初始化機制略簡略。
類的初始化也是延遲的,直到類第一次被主動使用(active use),JVM才會初始化類。(參見《Java動態性: 類加載時的延遲初始化》)
類的初始化分兩步:
1.如果基類沒有被初始化,初始化基類
2.有類構造函數,則執行類構造函數 這是由java編譯器完成的,將會把類成員變量的初始化和static區間的代碼提取出,放到一個叫<clinit>的方法中。
這個方法不能被一般的method訪問(static final 成員變量不會在此執行初始化,它一般被編譯器生成constant值)。
如果你細心,應該會注意到,<clinit>中是不會顯示的調用基類的<clinit>的,因為1中已經執行了基類的初始化。
類的初始化還必須注意線程安全的問題。
另外,注意,接口是的初始化比較不同。
這就解釋了為什麼接口不能定義成員變量,只能定義static final變量:
1.接口不可實例化,即其中的信息都應該是類層次的,而不是對象層次。很容易理解為static了。
2.如果能修改,一旦一個類實現了這個接口,並修改了接口中的非final變量,則對於其他在之後實現同樣接口的類,他們實現的就不是同一個接口了,接口中的變量都變化了。
綜上述,static final更適合於接口。
再另外
注意class和interface的初始化策略是不一樣的。
一個class的初始化,會首先初始化superclass,而interface的初始化,只是因為其中的non constant變量(比如final static i = random())被使用,而不管super interface。