目錄
驗證
為什麼靜态塊先執行,在什麼時候執行
JVM類加載過程
靜态代碼塊執行
為什麼普通代碼塊優于構造方法執行
誤區探究:靜态代碼塊,會在類被加載時自動執行嗎
【結論】
靜态代碼塊 ==> main方法 ==> 普通代碼塊 ==> 構造方法
- 如果有繼承關系,父類先由于子類執行
- 靜态代碼塊隻在類加載時執行一次(後面再執行個體化對象時,該類已經加載過,是以靜态塊不再執行)
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsICM38FdsYkRGZkRG9lcvx2bjxiNx8VZ6l2cs0TPR5UMrpWT6lkeNBDOsJGcohVYsR2MMBjVtJWd0ckW65UbM5WOHJWa5kHT20ESjBjUIF2X0hXZ0xCMx81dvRWYoNHLrdEZwZ1Rh5WNXp1bwNjW1ZUba9VZwlHdssmch1mclRXY39CXldWYtlWPzNXZj9mcw1ycz9WL49zZuBnLxYTNzEjM0kTMwMTMxAjMwIzLc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
驗證
class MyClass {
public MyClass() {
System.out.println("父類構造方法");
}
static {
System.out.println("父類靜态代碼塊");
}
{
System.out.println("父類非靜态代碼塊");
}
public static void method() {
System.out.println("父類普通方法執行");
}
}
public class One extends MyClass{
public One() {
System.out.println("子類構造方法");
}
static {
System.out.println("子類靜态代碼塊");
}
{
System.out.println("子類非靜态代碼塊");
}
public static void method() {
System.out.println("子類普通方法執行");
}
public static void main(String[] args) {
System.out.println("子類main方法執行");
MyClass once = new One();
once.method();
System.out.println("------------");
//第二次類加載, 父類和子類得靜态代碼塊不再執行
MyClass twice = new One();
twice.method();
}
}
執行結果:
- 父類靜态代碼塊
- 子類靜态代碼塊
- 子類main方法執行
- 父類非靜态代碼塊
- 父類構造方法
- 子類非靜态代碼塊
- 子類構造方法
- 父類普通方法執行
- ------------------------ 第二次執行個體化對象,父類和子類的靜态代碼塊不再執行
- 父類非靜态代碼塊
- 父類構造方法
- 子類非靜态代碼塊
- 子類構造方法
- 父類普通方法執行
為什麼靜态塊先執行,在什麼時候執行
JVM類加載過程
首先,得從JVM類加載器說起。可以參考我之前寫過的文章
🔗【探究JVM一】JVM的入口——類加載器子系統
一個類從 .java 到 .class 加載到JVM解釋執行,需要經曆三大步驟:
加載 ==> 連結 ==> 初始化
其中裝載階段又三個基本動作組成:
- 通過類型的完全限定名,産生一個代表該類型的二進制資料流
- 解析這個二進制資料流為方法區内的内部資料結
- 建構立一個表示該類型的java.lang.Class類的執行個體
另外如果一個類裝載器在預先裝載的時遇到缺失或錯誤的class檔案,它需要等到程式首次主動使用該類時才報告錯誤。
連結階段又分為三部分:
- 驗證,确認類型符合Java語言的語義,檢查各個類之間的二進制相容性(比如final的類不用擁有子類等),另外還需要進行符号引用的驗證。
- 準備,Java虛拟機為類變量配置設定記憶體,設定預設初始值。
- 解析(可選的) ,在類型的常量池中尋找類,接口,字段和方法的符号引用,把這些符号引用替換成直接引用的過程。
當一個類被主動使用時,JVM就會對其初始化,如下六種情況為主動使用:
- 當建立某個類的新執行個體時(如通過new或者反射,克隆,反序列化等)
- 當調用某個類的靜态方法時
- 當使用某個類或接口的靜态字段時
- 當調用Java API中的某些反射方法時,比如類Class中的方法,或者java.lang.reflect中的類的方法時
- 當初始化某個子類時
- 當虛拟機啟動某個被标明為啟動類的類(即包含main方法的那個類)
-
JDK1.7開始提供的動态語言支援,java.lang.invoke.MethodHandle執行個體的解析結果REF_getStatic,REF_putStatic,REF_invokeStatic句柄對應的類沒有初始化,則初始
被動使用
除了以上七種情況,其他使用Java類的方式都被看作是對類的被動使用,都不會導緻類的初始化
Java編譯器會收集所有的類變量初始化語句和類型的靜态初始化器,将這些放到一個特殊的<clinit>方法中
靜态代碼塊執行
實際上,static塊的執行發生在“初始化”的階段。初始化階段,JVM主要完成對靜态變量的初始化,靜态塊執行等工作。
下面我們看看執行static塊的幾種情況:
- 第一次new A()的過程會列印"";因為這個過程包括了初始化
- 第一次Class.forName("A")的過程會列印"";因為這個過程相當于Class.forName("A",true,this.getClass().getClassLoader());
- 第一次Class.forName("A",false,this.getClass().getClassLoader())的過程則不會列印""。因為false指明了裝載類的過程中,不進行初始化。不初始化則不會執行static塊。
為什麼普通代碼塊優于構造方法執行
通過檢視編譯完成的 .class檔案 發現,普通代碼塊實際上是被放到了構造方法中,且是放在了構造方法的第一句
那麼就不難解釋:為什麼普通代碼塊會比構造方法執行順序考前了。
在執行子類的構造方法時,子類 super 指向父類的構造方法會先執行父類的構造方法。由于靜态代碼塊執行是優先于main方法的,是以先回去執行父類的靜态代碼塊再執行子類的靜态代碼塊。此時 super 由于指向父類需要去執行父類的構造方法,且普通代碼塊會被轉換到構造方法的第一行,是以此時就會執行父類的代碼塊以及構造方法。當super執行完畢回到子類時,由于子類的代碼塊也被放到了構造方法中,且在super之後是以執行子類代碼塊再執行子類構造方法。
因為 main 方法池Java程式執行的入口,是以優于構造方法和普通代碼塊的執行;但是,靜态塊是在類加載時的初始化階段就執行了,此時 main方法還沒有進入到虛拟機棧中,是以是靜态代碼塊最先執行。
【文章參考】
[1] [美] 文納斯. 《深入Java虛拟機》第二版
[2] oracle docs. https://docs.oracle.com/javase/7/docs/api/java/lang/Class.html#forName%28java.lang.String,%20boolean,%20java.lang.ClassLoader%29
[3] oracle docs. https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-5.html