JVM類加載器、代碼初始化執行順序

(1)下面第一種和第二種會初始化A執行它的static裏面的代碼塊,但是第三種不會,主要原因就在於第三種情況訪問的A的靜態變量是靜態常量,所以雖然是主動調用了A,但是不會去初始化A,這算是靜態常量的特殊性。JVM01是入口類,所以它的靜態代碼塊是肯定要執行的。

public class JVM01 {
    static {
        System.out.println("static main block");
    }

    public static void main(String[] args){
        System.out.println(A.x);
    }
}

class A {
//    static final int x = new Random().nextInt(100);
//    static int x = 10;
    static final int x = 10;

    static {
        System.out.println("static A block");
    }
}

(2)一般情況下,主動調用子類,會先初始化父類;反之主動調用父類的話,不會初始化子類,否則主動調用Object的話,所有的類都要初始化了。當然,如果父類已經被主動調用並初始化過了,再主動調用子類,就不會再去初始化父類了,前提是在同一個類加載器中。

public class JVM01 {

    static {
        System.out.println("static main block");
    }

    public static void main(String[] args){
        System.out.println(Child.y);
    }
}

class Parent {

    static int x = 10;

    static {
        System.out.println("static parent block");
    }
}

class Child extends Parent{

    static int y = 20;

    static {
        System.out.println("static child block");
    }
}

結果是:

static main block
static parent block
static child block
20

(3)前面說主動調用子類會先初始化父類,這個主動調用時有條件的,必須調用的是子類自己的靜態變量或靜態方法,意思是說必須在自己的類裏面定義的,如果是父類裏面定義的,雖然能調用,但因爲不是在自己類裏面定義,所以調用時不能算是主動調用,所以不會初始化子類,而是直接初始化這個靜態變量或靜態方法定義所在的父類。

public class JVM01 {

    static {
        System.out.println("static main block");
    }

    public static void main(String[] args){
        System.out.println(Child.x);
        Child.doSomething();
    }
}

class Parent {

    static int x = 10;

    static {
        System.out.println("static parent block");
    }

    static void doSomething(){
        System.out.println("do something");
    }
}

class Child extends Parent{

    static int y = 20;

    static {
        System.out.println("static child block");
    }
}

結果是:

static main block
static parent block
10
do something

(4)哪些算是主動調用?

  1. 創建類的實例,即new一個,如果只是Parent parent;還不算,需要parent = new Parent();時纔算是創建。
  2. 訪問某個類的靜態變量(除final常量)或靜態方法,或者對靜態變量賦值。
  3. 反射,就是`Class.forName(“xxx.xxx”)。
  4. 初始化它的子類,如果它沒被初始化過,也算是主動調用了它進行初始化。
  5. 啓動類。

(5)自然的,其他情況不算是主動調用,不會初始化。比如使用ClassLoader去加載類的話,就不算主動調用,也就不會初始化。

public class JVM01 {

    static {
        System.out.println("static main block");
    }

    public static void main(String[] args) throws ClassNotFoundException {

        ClassLoader classLoader = ClassLoader.getSystemClassLoader();

        Class<?> clazz = classLoader.loadClass("Parent");

        System.out.println("=============");

        clazz = Class.forName("Parent");
    }
}

class Parent {

    static int x = 10;

    static {
        System.out.println("static parent block");
    }
}

結果是:

static main block
=============
static parent block

(6)類加載器工作流程

  1. 類的加載。把.class文件中的爲禁止數據讀取到內存中,放在內存的方法區,並且在堆區創建一個相應的java.lang.Class對象,用來封裝類在方法區裏的數據結構。
  2. 類的連接。連接主要有3個步驟,一個是驗證,就是檢查是否滿足一些java標準或者檢查引用之間的正確性等;二是準備,主要是爲靜態變量分配內存,並且初始化默認值;三是解析,主要是把符號引用轉化成直接引用,也就是說如果在一個類裏面有另一個類方法的引用,那麼就會把這行代碼直接替換成一個指針,這個指針指向這另一個類方法在內存中的地址,這就是轉成直接引用。
  3. 類的初始化。這就是根據代碼中的定義來給靜態變量賦值。

也就是說,我們的靜態變量有可能要經過兩次賦值,第一次是賦默認值,第二次是賦值我們寫的值。賦值我們自己寫的值也是依次從上往下來執行標記有static的代碼。

public class JVM01 {

    public static void main(String[] args) throws ClassNotFoundException {
        Parent parent = Parent.getInstance();
        System.out.println(Parent.x);
        System.out.println(Parent.y);
    }
}

class Parent {

    static Parent singleton = new Parent();

    static int x = 10;

    static int y;

    private Parent(){
        x++;
        y++;
    }

    public static Parent getInstance(){
        return singleton;
    }
}

上面代碼,當調用getInstance的時候就算是主動調用了,所以開始初始化,初始化有順序,所以先執行第一行,也就是一個構造函數,xy都賦值爲1,然後執行第二行和第三行,把x重新賦值了10,y不變,所以最終結果是:

10
1

如果靜態代碼順序換一下,如下:

static int x = 10;

static int y;

static Parent singleton = new Parent();

那麼結果就是:

11
1
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章