1. 程式人生 > >JVM類載入器

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