Java虛擬機經過裝載、鏈接、初始化來使得一個Java類型能夠被Java程序所使用,以下圖所示,其中鏈接過程又分爲驗證、準備、解析三個部分。其中部分類的解析過程能夠推遲到程序真正使用其某個符號引用時再去解析。
解析過程能夠推遲到類的初始化以後再進行,但這是有條件的,Java虛擬機必須在每一個類或接口主動使用時進行初始化。
如下爲主動使用的狀況:java
主動使用會致使類的初始化,其超類均將在該類的初始化以前被初始化,但經過子類訪問父類的靜態字段或方法時,對於子類(或子接口、接口的實現類)來講,這種訪問就是被動訪問,或者說訪問了該類(接口)中的不在該類(接口)中聲明的靜態成員。
如:
Grandpa的定義以下:安全
package com.ice.passiveaccess; public class Grandpa { static{ System.out.println("Grandpa was initialized."); } }
Parent的定義以下:數據結構
package com.ice.passiveaccess; public class Parent extends Grandpa{ static String language = "Chinese"; static{ System.out.println("Parent was initialized."); } }
Cindy的定義以下:函數
package com.ice.passiveaccess; public class Cindy extends Parent{ static{ System.out.println("Child was initialized."); } }
如今經過Cindy訪問父類的language成員this
package com.ice.passiveaccess; public class PassiveAccessTest { public static void main(String args[]){ System.out.println(Cindy.language); } }
結果以下:code
可見這是被動訪問,Cindy自身並無初始化對象
下面簡要介紹裝載、驗證與初始化過程:
1.裝載:繼承
在加載器的相關代碼中能夠看到,最終經過defineClass()建立一個Java類型對象(Class對象)。遞歸
2.驗證:
class文件校驗器須要四趟獨立的掃描來完成驗證工做,其中:
第一趟掃描在裝載時進行,會對class文件進行結構檢查,如接口
第二趟掃描在鏈接過程當中進行,會對類型數據進行語義檢查,主要檢查各個類的二進制兼容性(主要是查看超類和子類的關係)和類自己是否符合特定的語義條件
第三趟掃描爲字節碼驗證,其驗證內容和實現較爲複雜,主要檢驗字節碼是否能夠被java虛擬機安全地執行。
第四趟掃描在解析過程當中進行,爲對符號引用的驗證。在動態鏈接過程當中,經過保存在常量池的符號引用查找被引用的類、接口、字段、方法時,在把符號引用替換成直接引用時,首先須要確認查找的元素真正存在,而後須要檢查訪問權限、查找的元素是不是靜態類成員而非實例成員。
3.準備:
爲類變量分配內存、設置默認初始值(內存設置初始值,而非對類變量真正地進行初始化,即類中聲明int i = 5,但實際上這裏是分配內存並設置初始值爲0)
4.解析:
在類的常量池中尋找類、接口、字段、方法的符號引用,將這些符號引用替換成直接引用
5.初始化:
對類變量賦予指定的初始值(這個時候int i = 5就必須賦予i以初值5)。這個初始值的給定方式有兩種,一種是經過類變量的初始化語句,一種是靜態初始化語句。而這些初始化語句都將被Java編譯器一塊兒放在方法中。
如前面所述,一個類的初始化須要初始化其直接超類,並遞歸初始化其祖先類,初始化是經過調用類的初始化方法完成的。此外,對於接口,並不須要初始化其父接口,而只須要執行該接口的接口初始化方法就能夠了。
注意:
如UsefulParameter類以下:
Class UsefulParameter{ static final int height = 2; static final int width = height * 2; }
類Area的類變量初始化以下:
Class Area{ static int height = UsefulParameter.height * 2 ; static int width = UsefulParameter.width * 2; }
在Area的< clinit>中,將直接把二、4嵌入到字節碼中
6.類實例化
這裏須要明白什麼是類初始化,什麼是類實例化,以及類的實例對象的初始化
如前面所述,類初始化時對類(靜態)變量賦予指定的初始值,類初始化以後就能夠訪問類的靜態字段和方法,而訪問類的非靜態(實例)字段和方法,就須要建立類的對象實例,故類的實例化是在類的初始化以後,是在堆上建立一個該類的對象。
類的靜態方法和字段屬於類,做爲類型數據保存在方法區,其生命週期取決於類,而實例方法和字段位於Java堆,其生命週期取決於對象的生命週期。
類的初始化會從祖先類到子類、按出現順序,對類變量的初始化語句、靜態初始化語句塊依次進行初始化。而對類實例的初始化也相似,會從祖先類到子類、按出現順序,對類成員的初始化語句、實例初始化塊、構造方法依次進行初始化。
好比:
package com.ice.init; public class Parent { public static int i = print("parent static:i"); public int ii = print("parent:ii"); static{ print("父類靜態初始化"); } { print("父類實例初始化"); } public Parent(String str) { System.out.println("parent constructor:" + str); } public static int print(String str){ System.out.println("initial:" + str); return i; } }
子類Child以下:
package com.ice.init; public class Child extends Parent{ public static int i = print("child static:i"); public int ii = print("child:ii"); static{ print("子類靜態初始化"); } { print("子類實例初始化"); } public Child(String str) { super(str); System.out.println("Child constructor:" + str); } public static int print(String str){ System.out.println("initial:" + str); return i; } public static void main(String args[]){ Child child = new Child("cindy"); } }
其初始化順序爲:
Java編譯器爲每一個類生成了至少一個實例初始化方法< init >,一個< init >方法分爲三部分: 另外一個初始化方法< init >(),對任意實例成員的初始化的字節碼,構造方法的方法體的字節碼
< init >方法的調用以下:
若< init >指明從this()方法明確調用另外一個構造方法,那麼將調用另外一個構造方法,不然,若該類有直接超類,那麼,若< init >指明從super()方法明確調用其超類的構造方法,那麼將調用超類的構造方法,不然,將默認調用超類的無參構造方法。這樣,將從其祖先類到該 類,分別完成對應的實例成員的初始化(可能被子類覆蓋)
接下來以一道題結束本節:
判斷輸出:
package com.ice.init; class T implements Cloneable{ public static int k = 0; public static T t1 = new T("t1"); public static T t2 = new T("t2"); public static int i = print("i"); public static int n = 99; public int j = print("j"); { print("構造塊"); } static { print("靜態塊"); } public T(String str) { System.out.println((++k) + ":" + str + " i=" + i + " n=" + n); ++n; ++ i; } public static int print(String str){ System.out.println((++k) +":" + str + " i=" + i + " n=" + n); ++n; return ++ i; } public static void main(String[] args){ T t = new T("init"); } }
題解以下:
(1). 首先T類被加載、鏈接後進行初始化,會先對字段k、t一、t二、i、n以及static塊進行初始化。
(2). t1實例的初始化會初始化實例成員j,(實際上先進行父類實例內容的初始化)先調用靜態方法print,並執行實例初始化塊{},輸出:
1: j i=0 n= 0(i和n都尚未初始化)
2:構造塊 i=1 n=1
(3). 隨後調用t1實例的構造函數,輸出:
3:t1 i=2 n=2
(4). 相似有t2實例的初始化:
4: j i=3 n= 3
5:構造塊 i=4 n=4
6:t2 i=5 n=5
(5). i的初始化:
7.i i=6 n=6
(6). n的初始化和靜態塊的初始化:
8.靜態塊 i=7 n=99(n已經被初始化)
(7). t實例的初始化: 9.j i=8 n= 100 10.構造塊 i=9 n= 101 11.init i=10 n= 102