標籤:組元 java cat 自身 9.png obj 空間 off ram
一、Java類載入機制
1.概述
Class檔案由類裝載器裝載後,在JVM中將形成一份描述Class結構的元資訊對象,通過該元資訊對象可以獲知Class的結構資訊:如建構函式,屬性和方法等,Java允許使用者藉由這個Class相關的元資訊對象間接調用Class對象的功能。
虛擬機器把描述類的資料從class檔案載入到記憶體,並對資料進行校正,轉換解析和初始化,最終形成可以被虛擬機器直接使用的Java類型,這就是虛擬機器的類載入機制。
2.工作機制
類裝載器就是尋找類的位元組碼檔案,並構造出類在JVM內部表示的對象組件。在Java中,類裝載器把一個類裝入JVM中,要經過以下步驟:
(1) 裝載:尋找和匯入Class檔案;
(2) 連結:把類的位元據合并到JRE中;
(a)校正:檢查載入Class檔案資料的正確性;
(b)準備:給類的靜態變數分配儲存空間;
(c)解析:將符號引用轉成直接引用;
(3) 初始化:對類的靜態變數,靜態代碼塊執行初始化操作
Java程式可以動態擴充是由運行期動態載入和動態連結實現的;比如:如果編寫一個使用介面的應用程式,可以等到運行時再指定其實際的實現(多態),解析過程有時候還可以在初始化之後執行;比如:動態綁定(多態);
【類初始化】
(1) 遇到new、getstatic、putstatic或invokestatic這4條位元組碼指令時,如果類沒有進行過初始化,則需要先觸發其初始化。產生這4條指令的最常見的Java代碼情境是:使用new關鍵字執行個體化對象的時候,讀取或設定一個類的靜態欄位(被final修飾、已在編譯期把結果放入常量池的靜態欄位除外)的時候,以及調用一個類的靜態方法的時候。
(2) 使用java.lang.reflect包的方法對類進行反射調用的時候,如果類沒有進行過初始化,則需要先觸發其初始化。
(3) 當初始化一個類的時候,如果發現其父類還沒有進行過初始化,則需要先觸發其父類的初始化。
(4)當虛擬機器啟動時,使用者需要指定一個要執行的主類(包含main()方法的那個類),虛擬機器會先初始化這個主類。
只有上述四種情況會觸發初始化,也稱為對一個類進行主動引用,除此以外,所有其他方式都不會觸發初始化,稱為被動引用
代碼清單1
上述代碼運行後,只會輸出【---SuperClass init】, 而不會輸出【SubClass init】,對於靜態欄位,只有直接定義這個欄位的類才會被初始化,因此,通過子類來調用父類的靜態欄位,只會觸發父類的初始化,但是這是要看不同的虛擬機器的不同實現。
代碼清單2
此處不會引起SuperClass的初始化,但是卻觸發了【[Ltest.SuperClass】的初始化,通過arr.toString()可以看出,對於使用者代碼來說,這不是一個合法的類名稱,它是由虛擬機器自動產生的,直接繼承於Object的子類,建立動作由位元組碼指令newarray觸發,此時數組越界檢查也會伴隨數組對象的所有調用過程,越界檢查並不是封裝在數組元素訪問的類中,而是封裝在數組訪問的xaload,xastore位元組碼指令中.
代碼清單3
對常量ConstClass.value 的引用實際都被轉化為NotInitialization類對自身常量池的引用,這兩個類被編譯成class後不存在任何聯絡。
【裝載】
在裝載階段,虛擬機器需要完成以下3件事情
(1) 通過一個類的全限定名來擷取定義此類的二進位位元組流
(2) 將這個位元組流所代表的靜態儲存結構轉化為方法區的運行時資料結構
(3) 在Java堆中產生一個代表這個類的java.lang.Class對象,作為方法區這些資料的訪問入口。
虛擬機器規範中並沒有準確說明二進位位元組流應該從哪裡擷取以及怎樣擷取,這裡可以通過定義自己的類載入器去控制位元組流的擷取方式。
【驗證】
虛擬機器如果不檢查輸入的位元組流,對其完全信任話,很可能會因為載入了有害的位元組流而導致系統奔潰。
【準備】
準備階段是正式為類變數分配並設定類變數初始值的階段,這些記憶體都將在方法區中進行分配,需要說明的是:
這時候進行記憶體配置的僅包括類變數(被static修飾的變數),而不包括執行個體變數,執行個體變數將會在對象執行個體化時隨著對象一起分配在Java堆中;這裡所說的初始值“通常情況”是資料類型的零值,假如:
public static int value = 123;
value在準備階段過後的初始值為0而不是123,而把value賦值的putstatic指令將在初始化階段才會被執行
二、類載入器與雙親委派模型
類載入器
(1) Bootstrap ClassLoader : 將存放於<JAVA_HOME>\lib目錄中的,或者被-Xbootclasspath參數所指定的路徑中的,並且是虛擬機器識別的(僅按照檔案名稱識別,如 rt.jar 名字不符合的類庫即使放在lib目錄中也不會被載入)類庫載入到虛擬機器記憶體中。啟動類載入器無法被Java程式直接引用
(2) Extension ClassLoader : 將<JAVA_HOME>\lib\ext目錄下的,或者被java.ext.dirs系統變數所指定的路徑中的所有類庫載入。開發人員可以直接使用擴充類載入器。
(3) Application ClassLoader : 負責載入使用者類路徑(ClassPath)上所指定的類庫,開發人員可直接使用。
雙親委派模型
工作過程:如果一個類載入器接收到了類載入的請求,它首先把這個請求委託給他的父類載入器去完成,每個層次的類載入器都是如此,因此所有的載入請求都應該傳送到頂層的啟動類載入器中,只有當父載入器反饋自己無法完成這個載入請求(它在搜尋範圍中沒有找到所需的類)時,子載入器才會嘗試自己去載入。
好處:java類隨著它的類載入器一起具備了一種帶有優先順序的層次關係。例如類java.lang.Object,它存放在rt.jar中,無論哪個類載入器要載入這個類,最終都會委派給啟動類載入器進行載入,因此Object類在程式的各種類載入器環境中都是同一個類。相反,如果使用者自己寫了一個名為java.lang.Object的類,並放在程式的Classpath中,那系統中將會出現多個不同的Object類,java類型體系中最基礎的行為也無法保證,應用程式也會變得一片混亂。
java.lang.ClassLoader中幾個最重要的方法:
//載入指定名稱(包括包名)的二進位類型,供使用者調用的介面public Class<?> loadClass(String name);//載入指定名稱(包括包名)的二進位類型,同時指定是否解析(但是,這裡的resolve參數不一定真正能達到解析的效果),供繼承用protected synchronized Class<?> loadClass(String name, boolean resolve);protected Class<?> findClass(String name)//定義類型,一般在findClass方法中讀取到對應位元組碼後調用,可以看出不可繼承(說明:JVM已經實現了對應的具體功能,解析對應的位元組碼,產生對應的內部資料結構放置到方法區,所以無需覆寫,直接調用就可以了)protected final Class<?> defineClass(String name, byte[] b, int off, int len) throws ClassFormatError{}
如下是實現雙親委派模型的主要代碼:
三、反射
Reflection機制允許程式在正在執行的過程中,利用Reflection APIs取得任何已知名稱的類的內部資訊,包括:package、 type parameters、 superclass、 implemented interfaces、 inner classes、 outer classes、 fields、 constructors、 methods、 modifiers等,並可以在執行的過程中,動態產生instances、變更fields內容或喚起methods。
1、擷取構造方法
Class類提供了四個public方法,用於擷取某個類的構造方法。
Constructor getConstructor(Class[] params)
根據建構函式的參數,返回一個具體的具有public屬性的建構函式
Constructor getConstructors()
返回所有具有public屬性的建構函式數組
Constructor getDeclaredConstructor(Class[] params)
根據建構函式的參數,返回一個具體的建構函式(不分public和非public屬性)
Constructor getDeclaredConstructors()
返回該類中所有的建構函式數組(不分public和非public屬性)
2、擷取類的成員方法
與擷取構造方法的方式相同,存在四種擷取成員方法的方式。
Method getMethod(String name, Class[] params)
根據方法名和參數,返回一個具體的具有public屬性的方法
Method[] getMethods()
返回所有具有public屬性的方法數組
Method getDeclaredMethod(String name, Class[] params)
根據方法名和參數,返回一個具體的方法(不分public和非public屬性)
Method[] getDeclaredMethods()
返回該類中的所有的方法數組(不分public和非public屬性)
3、擷取類的成員變數(成員屬性)
存在四種擷取成員屬性的方法
Field getField(String name)
根據變數名,返回一個具體的具有public屬性的成員變數
Field[] getFields()
返回具有public屬性的成員變數的數組
Field getDeclaredField(String name)
根據變數名,返回一個成員變數(不分public和非public屬性)
Field[] getDelcaredFields()
返回所有成員變數組成的數組(不分public和非public屬性)
Java:類載入機制及反射