虛擬機器類載入機制(七)——類載入的過程(初始化)
類初始化時類載入過程的最後一步,前面的類載入過程中,除了在載入階段(類載入過程的一個階段)應用程式可以通過自定義類載入器參與之外,其餘動作完全由虛擬機器主導和控制。到了初始化階段,才真正開始執行類中定義的java程式程式碼。
在準備階段,變數已經賦過一次系統要求的初始值,而在初始化階段,則根據程式設計師通過程式制定的主觀計劃去初始化類變數和其他資源,或者可以從另外一個角度來表達:初始化階段是執行類構造器<clinit>()方法的過程。
<clinit>()方法執行過程中可能會影響程式執行行為:
(1)<clinit>()方法是由編譯器自動收集類中的所有類變數的賦值動作
(2)<clinit>()方法與類的建構函式(即類的例項構造器<init>()方法)不同,它不需要顯式地呼叫父類構造器,虛擬機器會保證在子類的<clinit>()方法執行之前,父類的<clinit>()方法已經執行完畢。因此在虛擬機器中第一個被執行的<clinit>()方法一定是java.lang.Object。
(3)由於父類的<clinit>()方法先執行,也就意味著父類中定義的靜態語句塊要優先於子類的變數賦值操作。
(4)<clinit>()方法對於類(抽象類)或介面來說並不是必需的,如果一個類中沒有靜態語句塊,也沒有對變數的賦值操作,那麼編譯器可以不為這個類生成<clinit>()方法。
(5)介面中不能使用靜態語句塊,但仍然有變數初始化的賦值操作,因此介面與類一樣都會生成<clinit>()方法。但介面與類不同的是,執行介面的<clinit>()方法不需要先執行父介面的<clinit>()方法。只有當父介面中定義的變數使用時,父接口才會初始化。另外,介面的實現類在初始化時也一樣不會執行介面的<clinit>()方法。
(6)虛擬機器會保證一個類的<clinit>()方法在多執行緒環境中被正確的加鎖,同步,如果多個執行緒同時去初始化一個類,那麼只會有一個執行緒去執行這個類的<clinit>()方法,其他執行緒都需要阻塞等待,直到活動執行緒執行<clinit>()方法完畢。如果在一個類的<clinit>()方法中有耗時很長的操作,就可能造成多個程序阻塞(需要注意的是:其他執行緒雖然會被阻塞,但如果執行<clinit>()方法的那條執行緒退出<clinit>()方法後,其他執行緒喚醒之後不會再次進入<clinit>()方法。同一個類載入器下,一個型別只會初始化一次),在實際應用中這種阻塞往往是很隱蔽的。
參考文件:《深入理解java虛擬機器》周志明著