您好,登錄后才能下訂單哦!
如何理解Java JVM虛擬機中init和clinit的區別,很多新手對此不是很清楚,為了幫助大家解決這個難題,下面小編將為大家詳細講解,有這方面需求的人可以來學習下,希望你能有所收獲。
<clinit>:在jvm第一次加載class文件時調用,包括靜態變量初始化語句和靜態塊的執行。
<init>:在實例創建出來的時候調用,包括調用new操作符;調用Class或Java.lang.reflect.Constructor對象的newInstance()方法;調用任何現有對象的clone()方法;通過java.io.ObjectInputStream類的getObject()方法反序列化。
<init>是對象構造器方法,也就是說在程序執行 new 一個對象調用該對象類的 constructor 方法時才會執行init方法,而<clinit>是類構造器方法,也就是在jvm進行類加載—–驗證—-解析—–初始化,中的初始化階段jvm會調用clinit方法。
<clinit>是instance實例構造器,對非靜態變量解析初始化,而clinit是class類構造器對靜態變量,靜態代碼塊進行初始化。<clinit>是由javac添加的靜態方法,并且在加載類之后由JVM調用。可以在類字節碼中使用字節碼大綱工具看到這種方法。注意,<clinit>只有當一個類需要靜態初始化時才添加,具體代碼如下:
public class Test1 {
static int x = 1;
public static void main(String[] args) throws Exception {
}
}
public class Test2 {
static final int x = 1;
public static void main(String[] args) throws Exception {
}
}
Test1類中有<clinit>,因為它的變量x需要使用1初始化;而Test2沒有<clinit>方法,因為它x是一個常數。還有一點是Class.forName有boolen intialize參數確定在加載后是否應該初始化類。
<clinit>在準備階段,變量已經賦過一次系統要求的初始值,而在初始化階段,則根據程序員通過程序制定的主觀計劃去初始化類變量和其他資源,或者可以從另外一個角度來表達:初始化階段是執行類構造器<clinit>方法的過程。
<clinit>方法是由編譯器自動收集類中的所有類變量的賦值動作和靜態語句塊(static{}塊)中的語句合并產生的,編譯器收集的順序是由語句在源文件中出現的順序所決定的,靜態語句塊中只能訪問到定義在靜態語句塊之前的變量,定義在它之后的變量,在前面的靜態語句塊可以賦值,但是不能訪問如下代碼
public class Test{
static{
i=0;//給變量賦值可以正常編譯通過
System.out.print(i);//這句編譯器會提示"非法向前引用"
}
static int i=1;
}
虛擬機JVM會保證在子類的<clinit>方法執行之前,父類的<clinit>方法已經執行完畢。 因此在虛擬機中第一個被執行的<clinit>方法的類肯定是java.lang.Object。由于父類的<clinit>方法先執行,也就意味著父類中定義的靜態語句塊要優先于子類的變量賦值操作,如下代碼中,字段B的值將會是2而不是1。
static class Parent{
public static int A=1;
static{
A=2;
}
static class Sub extends Parent{
public static int B = A;
}
public static void main(String[] args){
System.out.println(Sub.B);
}
}
注意:接口中屬性都是static final類型的常量,在準備階段就已經初始化完成了。
接口中不能使用靜態語句塊,但仍然有變量初始化的賦值操作,因此接口與類一樣都會生成<clinit>方法。 但接口與類不同的是,執行接口的<clinit>方法不需要先執行父接口的<clinit>方法。 只有當父接口中定義的變量使用時,父接口才會初始化。另外,接口的實現類在初始化時也一樣不會執行接口的<clinit>方法。
JVM類加載原理
1)類的生命周期包括了:加載(Loading)、驗證(Verification)、準備(Preparation)、解析(Resolution)、初始化(Initialization)、使用(Using)、卸載(Unloading)七個階段
2)當Java程序需要使用某個類時,JVM會確保這個類已經被加載、連接(驗證、準備和解析)和初始化。
3)加載階段:通過一個類的全限定名來獲取此類的二進制字節流;將這個字節流所代表的靜態存儲結構轉化為方法區的運行時數據結構;在java堆中生成一個代表這個類的Class對象,作為方法區這些數據的訪問入口;
4)驗證階段:驗證是為了確保Class文件的字節流中包含的信息符合當前虛擬機的要求,并且不會危害虛擬機自身的安全;包括文件格式驗證、元數據驗證、字節碼驗證、符號引用驗證;如果驗證到輸入的字節流不符合Class文件的存儲格式,就拋出一個java.lang.VerifyError異常或其子類異常。
5)準備階段:準備階段是正式為類變量分配內存并設置類變量初始值(各數據類型的零值)的階段,這些內存將在方法區中進行分配。
6) 解析階段:解析階段是在虛擬機將常量池內的符號引用替換為直接引用的過程。符號引用:符號引用以一組符號來描述所引用的目標,符號可以是任何形式的字面量,只要使用時能無歧義地定位到目標即可。符號引用與虛擬機實現的內存布局無關,引用的目標并不一定已經加載到內存中。直接引用:直接引用可以是直接指向目標的指針、相對偏移量或者一個能間接定位到目標的句柄。如果有了直接引用,那引用的目標必定已經在內存中存在。
7)初始化階段:初始化階段是執行類構造器<clinit>()方法的過程。
初始化是重點,需要清楚以下幾點:
1)<clinit>()方法是由編譯器自動收集類中的所有類變量的賦值動作和靜態語句塊(static{}塊)中的語句合并產生的,編譯器收集的順序是由語句在源文件中出現的順序決定的。靜態語句塊只能訪問到定義在靜態語句塊之前的變量,定義在它之后的變量,在前面的靜態語句塊中可以賦值,但是不能訪問。
2) 方法與實例構造器<clinit>()不同,不需要顯示的調用父類構造器,虛擬機會保證在子類的<clinit>()方法執行之前,父類的<clinit>()已經執行完畢。
3)<clinit>()方法對于類或接口來說不是必須的,如果一個類中沒有靜態語句塊也沒有對變量的賦值操作,那么編譯器可以不為這個類生成<clinit>()方法。
4)執行接口的<clinit>()不需要先執行父接口的<clinit>()方法,只有當父接口中定義的變量被使用時,父接口才會被初始化。接口的實現類在初始化時也不會執行接口的<clinit>()方法。
5)虛擬機會保證一個類的<clinit>()方法在多線程環境中被正確的加鎖和同步,如果多個線程同時去初始化一個類,則只會有一個線程去執行這個類的<clinit>()方法,其他線程需要阻塞等待。
看完上述內容是否對您有幫助呢?如果還想對相關知識有進一步的了解或閱讀更多相關文章,請關注億速云行業資訊頻道,感謝您對億速云的支持。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。