WangScaler: 一個用心創做的做者。html
聲明:才疏學淺,若有錯誤,懇請指正。java
java類的生命週期包括加載、鏈接(驗證、準備、解析)、初始化、使用、卸載五個階段。而解析階段會在初始化以前或以後觸發。類的加載不是隨着jvm的啓動而加載,而是隨着使用動態的加載。緩存
接下來咱們首先了解下虛擬機的類加載機制。安全
咱們知道java的優點之一就是跨平臺性,爲何java能跨平臺執行呢?就由於java是運行在java虛擬機jvm上的。那麼jvm的類加載機制是怎樣的呢?咱們知道java編譯以後的文件是class文件,而虛擬機的類加載機制就是把Class文件加載到內存,進行校驗,解析和初始化的過程。markdown
加載就是經過類的全限定名來獲取到class文件,將文件的二進制字節流轉化成方法區的靜態數據結構,而後在內存中生成這個類的class對象並在堆中生成一個便於用戶調用的class類型的對象。數據結構
驗證就是對文件格式、元數據、字節碼進行驗證(即語法語義的驗證)、符號引用的驗證,確保Class文件中的字節流不會危害虛擬機的安全。可參考java虛擬機符號引用驗證_深刻了解Java虛擬機---虛擬機類加載機制。app
給靜態變量賦初值0。jdk8以前類的元信息、常量池、靜態變量都是存儲在永久代(方法區),而jdk8以後元空間(方法區)替代了永久代只存儲類的元信息,將常量池和靜態變量轉移至堆內存中。jvm
將符號引用替換成直接引用。解析階段會在初始化以前或以後觸發。ide
一、假如A引用B(具體的實現類),編譯階段編譯A的時候,是沒法知道B是否被編譯的,因此編譯階段B會被符號所代替,這個符號就是B的地址。在解析的時候若是B還沒有加載,就會加載B,此時A中的符號將替換成真正的B的地址,這種稱爲靜態解析,此時的解析是在初始化以前發生。oop
二、若是A引用的是B的抽象方法或者接口。那麼只有在調用A的時候才知道具體的實現類是哪個。此時的解析是發生在初始化以後的,也被成爲動態解析。
三、虛擬機能夠對第一次的解析結果進行緩存,避免解析動做的重複執行。
類、對象的初始化順序:(靜態變量、靜態代碼塊)>(變量、代碼塊)>構造器。
下面以簡短的例子來演示初始化的過程。
package com.wangscaler.load;
/** * @author WangScaler * @date 2021/7/28 19:17 */
public class Father {
private int i = method();
private static int j = staticMethod();
static {
System.out.println("一、父類靜態代碼塊");
}
Father() {
System.out.println("二、父類構造器");
}
{
System.out.println("三、父類代碼塊");
}
private int method() {
System.out.println("四、父類方法");
return 1;
}
private static int staticMethod() {
System.out.println("五、父類靜態方法");
return 1;
}
}
複製代碼
package com.wangscaler.load;
/** * @author WangScaler * @date 2021/7/29 14:15 */
public class Son extends Father {
private int i = method();
private static int j = staticMethod();
static {
System.out.println("六、子類靜態代碼塊");
}
Son() {
System.out.println("七、子類構造器");
}
{
System.out.println("八、子類代碼塊");
}
public int method() {
System.out.println("九、子類方法");
return 2;
}
private static int staticMethod() {
System.out.println("十、子類靜態方法");
return 2;
}
public static void main(String[] args) {
}
}
複製代碼
例子如上,咱們執行上述的代碼,main方法裏面什麼都沒有,會有打印產生嗎?
執行結果以下:
五、父類靜態方法
一、父類靜態代碼塊
十、子類靜態方法
六、子類靜態代碼塊
複製代碼
爲何main方法裏面什麼都沒有也會打印呢?
由於
一、main方法所在的類優先加載並初始化,固會加載初始化Son這個類。
二、Son繼承自Father,因此又會優先初始化Father這個類。
三、Father從上依次往下初始化靜態變量和靜態代碼塊。
一、父類靜態代碼塊
四、Father加載完以後,加載Son,依然是從上往下依次初始化靜態變量和靜態代碼塊
六、子類靜態代碼塊
修改main方法,以下:
public static void main(String[] args) {
Son son = new Son();
}
複製代碼
執行結果以下:
五、父類靜態方法
一、父類靜態代碼塊
十、子類靜態方法
六、子類靜態代碼塊
四、父類方法
三、父類代碼塊
二、父類構造器
九、子類方法
八、子類代碼塊
七、子類構造器
複製代碼
前四個是毋庸置疑的,那麼main方法建立對象(new)時,此時是實例初始化。JVM爲每個類的每個構造方法都建立一個()方法,用於初始化實例變量,由虛擬機自行調用。
執行方法,首行是super(),因此執行父類的方法。
從上至下執行非靜態變量、非靜態代碼塊
三、父類代碼塊
執行完父類,繼續執行Son
八、子類代碼塊
修改main方法
public static void main(String[] args) {
Son son = new Son();
System.out.println("---------------------------wangscaler-----------------------------------");
Son son1 =new Son();
}
}
複製代碼
打印以下
五、父類靜態方法
一、父類靜態代碼塊
十、子類靜態方法
六、子類靜態代碼塊
四、父類方法
三、父類代碼塊
二、父類構造器
九、子類方法
八、子類代碼塊
七、子類構造器
------------------------------wangscaler--------------------------------
四、父類方法
三、父類代碼塊
二、父類構造器
九、子類方法
八、子類代碼塊
七、子類構造器
複製代碼
由上能夠看出,多個實例就有多個方法,執行過程同實例初始化,就不過多介紹。
咱們知道final、private修飾的方法和靜態方法不能被子類重寫。因而咱們在實例初始化的代碼狀況下修改Father。
public int method() {
System.out.println("四、父類方法");
return 1;
}
複製代碼
固然子類的method上須要添加註解@Override,由於此時的子類變成了重寫父類的method方法。
此次的執行結果是:
五、父類靜態方法
一、父類靜態代碼塊
十、子類靜態方法
六、子類靜態代碼塊
九、子類方法
三、父類代碼塊
二、父類構造器
九、子類方法
八、子類代碼塊
七、子類構造器
複製代碼
與實例初始化狀況不一樣的是,第五個打印語句,爲何呢?
由於在執行父類的方法的時候,當初始化非靜態變量i時,調用方法this.method(),而this指得是正在建立的對象Son,因此執行的是重寫以後的method方法。
遇到new、getstatic、putstatic、或者invokestatic 這4條字節碼指令,進行初始化。使用java.lang.reflect包的方法,對壘進行反射調用的時候,若是沒有初始化,則先觸發初始化。當使用JDK1.7的動態語言支持時,若是一個Java.lang.invoke.MethodHandle實例最後的解析結果REF_getStatic,REF_outStatic,REF_invokeStatic的方法句柄,而且這個方法句柄所對應的類沒有進行過初始化,則須要先觸發其初始化
類的初始化:
main方法所在類優先加載初始化
子類初始化時,優先初始化父類
類的初始化就是執行方法
實例的初始化:
類建立實例(new)初始化,執行方法
的首行是super(),對應父類的方法
由非靜態變量、非靜態代碼塊、構造器組成
非靜態變量、非靜態代碼塊從上至下依次執行、構造器最後執行
有幾個構造器就有幾個方法
重寫的方法
注意:如下條件下沒有方法:
- 沒有初始化語句或靜態初始化語句初始化;
- 僅包含static、 final修飾的類變量,而且類變量初始化語句是常量表達式;