1. 程式人生 > >Java類中各模組的載入順序的JVM本質理解

Java類中各模組的載入順序的JVM本質理解

Java類中各模組的載入順序只是表象,但至於為什麼我們需要理解JVM的載入本質原理。

話不多說,上程式碼,從分析程式碼開始:

public class Test1 {

    static{
        System.out.println("這是靜態程式碼塊");
    }

    {
        System.out.println("這是普通程式碼塊");
    }

    public static void method(){
        System.out.println("這是一個靜態方法");
    }

    public void method2
(){ System.out.println("這是一個普通方法"); } public Test1(){ System.out.println("這是一個無參構造方法"); } public static void main(String[] args) { } }

執行結果為:

這是靜態程式碼塊

分析如下:
首先,JVM在載入類的時候先找一個類的main()方法,這也是一個類的入口,如果沒有main()方法,這個類將無法執行。所以main()方法是類載入的前提。但是不是說,有了main()方法就必須先執行main()方法裡面的程式碼,這是大錯特錯的,就比如說你去打掃一個房間,房間的門牌號是你找到房間的前提。但是找到房間後並不是要先打掃門口的位置。
找到main()方法後,JVM開始初始化類,而static修飾的程式碼塊,是根據類載入而載入的,並且只會載入依次,下次再載入這個類時,因為類已經被載入過了,所以static程式碼塊也已經載入過了,所以不會再次執行static程式碼塊。而普通程式碼塊和構造方法是與物件有關的,而所有的程式碼都沒有物件的產生,所以也不會執行這些程式碼,所以非靜態程式碼全都沒有執行。雖然有被靜態修飾的方法,但是方法不呼叫不執行,所以輸出結果裡沒有方法的結果。

我們接著嘗試建立物件:

程式碼如下:

public static void main(String[] args) {
        Test1 t=new Test1();

    }

程式執行結果如下:

這是靜態程式碼塊
這是普通程式碼塊
這是一個無參構造方法

分析如下:
正如我們上面說的那樣,JVM在找到main方法後,開始初始化類,執行靜態程式碼塊,然後回到main()方法,執行main()方法裡面的程式碼, 建立物件後,程式中的非靜態程式碼塊和構造方法相繼執行,完成後回到main()方法,再執行main()方法下面的程式,我們可以修改下main()方法驗證下。

main()方法如下:

    public static void main(String[] args) {
        System.out.println("Hello");
        Test1 t=new Test1();
        System.out.println("World");

    }

結果如下:

這是靜態程式碼塊
Hello
這是普通程式碼塊
這是一個無參構造方法
World

輸出結果證明了我們的想法,那就是:
找到main()方法 —> 初始化靜態程式碼塊 —> 回到main()方法 —>執行main()方法裡面的程式,建立物件 —> 執行非靜態程式碼和構造方法 —> 再回到main()方法執行下面的語句

為了說明類與靜態程式碼是相關的,即類如果已經被載入了,再次載入類時,靜態程式碼塊也不會再次初始化。

修改main()方法如下:

    public static void main(String[] args) {
        Test1 t=new Test1();
        Test1 tt=new Test1();

    }

執行結果如下:

這是靜態程式碼塊
這是普通程式碼塊
這是一個無參構造方法
這是普通程式碼塊
這是一個無參構造方法

分析如下:
雖然連續建立了兩次物件,但類在找到main()方法後已經被載入過了,靜態程式碼塊也就是在這個時候載入的,再次建立物件時,只執行跟物件有關的程式碼塊,所以靜態程式碼塊跟類有關,只會跟類一起載入一次,而非靜態程式碼快隨著物件的建立而不斷載入。

是不是感覺已經明白的差不多了,彆著急,我們在看一個程式:

public class Test1 {
    static Test1 t1=new Test1();

    static{
        System.out.println("這是靜態程式碼塊");
    }

    {
        System.out.println("這是普通程式碼塊");
    }

    public static void method(){
        System.out.println("這是一個靜態方法");
    }

    public void method2(){
        System.out.println("這是一個普通方法");
    }

    public Test1(){
        System.out.println("這是一個無參構造方法");
    }


    public static void main(String[] args) {

    }

}

執行結果如下:

這是普通程式碼塊
這是一個無參構造方法
這是靜態程式碼塊

程式碼分析如下:
JVM在找到main()方法後,初始化類,去執行靜態程式碼,而靜態程式碼剛好是一個建立物件的過程,我們又知道非靜態程式碼才是跟物件有關的,所以會再次進入這個類去尋找非靜態程式碼塊,所以我們看到的結果是先輸出了普通程式碼塊和構造方法,建立物件完成後,JVM繼續初始化類,第一個靜態程式碼已經執行完畢,接下類執行下一個靜態程式碼塊,也就是我們看到的輸出了靜態程式碼塊。然後回到main()方法,main()方法裡面沒有內容,所以程式執行結束。

如果我們在main()裡面建立一個物件會怎麼樣呢?

修改main()方法如下:

    public static void main(String[] args) {
        Test1 t=new Test1();    
    }

執行結果如下:

這是普通程式碼塊
這是一個無參構造方法
這是靜態程式碼塊
這是普通程式碼塊
這是一個無參構造方法

分析如下:
前三個輸出結果如我們預料的一樣,當回到main()方法時,建立物件,就執行非靜態程式碼,那麼就會輸出後兩行結果。

如果我們仔細觀察會發現,static Test1 t1=new Test1()這行程式碼為什麼不是死迴圈呢,因為new Test1()會進入Test1()類,但是隻會執行非靜態程式碼,也就是不會再執行new Test1()了,所以自然不會死迴圈。但是如果去掉static那麼我們看一下結果:

這是靜態程式碼塊
Exception in thread "main" java.lang.StackOverflowError
at test.Test1.<init>
....
....

會報錯,是因為無限制建立物件的結果,因為在執行完靜態程式碼後,建立物件後會繼續執行非靜態程式碼,然後再建立物件,再執行非靜態程式碼…如此迴圈下去,記憶體被耗完,所以就會報錯。

再考慮一種情況,有兩個類:

 class Test2 {
    static{
        System.out.println("這是靜態程式碼塊");
    }

    {
        System.out.println("這是普通程式碼塊");
    }

    public static void method(){
        System.out.println("這是一個靜態方法");
    }

    public void method2(){
        System.out.println("這是一個普通方法");
    }

    public Test2(){
        System.out.println("這是一個無參構造方法");
    }


    public static void main(String[] args) {
        System.out.println("hhellp");
        Test1 t=new Test1();    
    }

}

   public class Test1{
       public static void main(String[] args) {
            Test2 t=new Test2();    
            Test2 tt=new Test2();
        }
   }

執行結果為:

這是靜態程式碼塊
這是普通程式碼塊
這是一個無參構造方法
這是普通程式碼塊
這是一個無參構造方法

程式碼分析:
JVM首先會載入main()方法所在的類,載入這個類的靜態方法,發現沒有後回到main()方法,執行這行程式碼 Test2 t=new Test2(),也就是建立Test2()的物件,所以JVM又要開始載入這個類,因為這個類之前沒有被載入過,所以會先執行靜態程式碼塊來初始化類,然後再執行普通程式碼塊然後使用構造方法,這也是前三行的結果,然後再次回到main()方法,執行這行程式碼 Test2 t=new Test2(),然後發現Test2()這個類已經被JVM載入過了,所以不需要初始化類,直接呼叫非靜態方法就可以了。

其實,上面說了那麼多,就一個道理而已:

靜態程式碼是與類有關的,類載入一次,靜態程式碼也就只會載入一次。 非靜態程式碼是與物件有關的,物件建立幾次就執行幾次。

載入順序只是表象,而JVM的底層載入才是真理呀!