Java類的載入過程
編譯:即javac的過程,即把.java檔案編譯成.class檔案,即編譯成位元組碼檔案,同時做一些型別以及格式的檢查。
類只有在要執行的時候才會被載入進JVM,即編譯後只有需要到這個類的時候才會把他載入進JVM執行,這種動態載入是依靠反射來實現的,一般來說一個class只會被載入一次,下一次就會從jvm的class快取中獲取,不會再去檔案系統中去獲取class檔案了。
下面具體說說類的載入過程:
類的裝載方式分兩種:
1.隱式裝載:即平時我們通過new產生物件時,他隱式的呼叫類裝載器把類就載入進jvm中。無法進行動態載入,即你new的這個 class物件必須是你程式程式碼編譯的時候有的。
2.顯式裝載:即利用Class.forname()來顯示的載入一個類。即可以實現動態載入,你可以在程式執行時載入進一個編譯時並沒有的 類。
類的裝載器(負責把類載入進jvm)
有三種類裝載器,為什麼要有三個類載入器,一方面是分工,各自負責各自的區塊,另一方面為了實現委託模型,那麼遇到要加 載某一個類,三個類裝載器之間是如何工作的,具體見另一篇《深入理解java類載入器ClassLoader》。
類裝載器是如何工作的,其實我們可以自己實現一個類載入器:
try { URL url = new URL( "file:/d:/test/lib/" ); //根據路徑 URLClassLoader urlCL = new URLClassLoader( new URL[]{url}); Class c = urlCL.loadClass("TestClassA" ); TestClassA object = (TestClassA)c.newInstance(); object.method(); }catch (Exception e){ e.printStackTrace(); }
從上面的程式碼應該能基本知道類載入器的實現。
接下來具體講講類載入的整個過程:
當我們想執行一個.class檔案的時候,java.exe會幫助我們找到JRE,接著找到位於JRE中的jvm.dll,這就是java虛擬機器,虛擬機器啟用後,會先做一些初始化的動作,一旦初始化動作完成後,就會產生第一個類載入器--Bootstrap Loader(它是由c++編寫的),然後Bootstrap Loader載入Launcher.java中的ExtClassLoader載入器,並設定其parent為null(因為其parent是Bootstrap
Loader,它是由c++編寫的,無法找到這個例項
ExtClassLoader。所以ExtClassLoader載入器和AppClassLoader載入器都是由Bootstrap
Loader載入的,parent與是誰載入的並沒有關係。
有了類載入器之後,那麼這三個載入器之間如何協同工作,可以看《深入理解java類載入器ClassLoader》。至於是如何把.class檔案載入進jvm的過程,從上面自定義一個類裝載器也可以看出。
這樣載入過程就差不多了,但是一個類要可以使用還要進行連結,初始化兩個過程。
載入-->連結-->初始化 其中連結還包括驗證-->準備--》解析三個過程
1、載入(即上面說的整個過程)
類的載入階段,主要是獲取定義此類的二進位制位元組流,並將這個位元組流所代表的靜態儲存結構轉化為方法區的執行時資料結 構,最後在Java堆中生成一個代表這個類的java.lang.Class物件作為方法區這些資料的訪問入口。相對於類載入過程的其他 階段,載入階段是開發期可控性最強的階段。我們可以通過定製不通的類載入器,也就是ClassLoader來控制二進位制位元組流的 獲取方式。
2、驗證
驗證,準備和解析其實都屬於連線階段,而驗證就是連線階段的第一步。這一階段主要是為了確保Class檔案的位元組流中包含 的資訊複合當前虛擬機器的要求,並且不會危害虛擬機器自身的安全。主要驗證過程包括:檔案格式驗證,元資料驗證,位元組碼驗 證以及符號引用驗證。
3、準備
準備階段正式為類變數分配記憶體並設定初始值。這裡的初始值並不是初始化的值,而是資料型別的預設零值。這裡提到的類 變數是被static修飾的變數,而不是例項變數。關於準備階段為類變數設定零值的唯一例外就是當這個類變數同時也被final 修飾,那麼在編譯時,就會直接為這個常量賦上目標值。
4、解析
解析時虛擬機器將常量池中的符號引用替換為直接引用的過程。
5、初始化
在準備階段,變數已經賦過一次系統要求的初始值,在初始化階段,則是根據程式設計師通過程式的主觀計劃區初始化類變數和其 他資源。Java虛擬機器規範規定了有4種情況必須立即對類進行初始化(載入,驗證,準備必須在此之前完成)
1)當使用new關鍵字例項化物件時,當讀取或者設定一個類的靜態欄位(被final修飾的除外)時,以及當呼叫一個類的靜態 方法時(比如構造方法就是靜態方法),如果類未初始化,則需先初始化。
2)通過反射機制對類進行呼叫時,如果類未初始化,則需先初始化。
3)當初始化一個類時,如果其父類未初始化,先初始化父類。
4)使用者指定的執行主類(含main方法的那個類)在虛擬機器啟動時會先被初始化。
除了上面這4種方式,所有引用類的方式都不會觸發初始化,稱為被動引用。如:通過子類引用父類的靜態欄位,不會導致子類 初始化;通過陣列定義來引用類,不會觸發此類的初始化;引用類的靜態常量不會觸發定義常量的類的初始化,因為常量在編 譯階段已經被放到常量池中了。
上面就是一個類載入並可以使用的整個過程,java的類載入這種只有需要的時候才載入進來的做法為記憶體節省了很大的空間。