1. 程式人生 > >Java-----類的 載入 和 反射

Java-----類的 載入 和 反射

   類的載入機制

類的載入指的是將類的. class檔案中的二進位制資料讀入到記憶體中.     

                          

類載入的過程包括了載入、驗證、準備、解析、初始化五個階段.    

                    

載入:查詢並載入類的二進位制資料.

1、根據類的全限定名(包名+類名)來獲取類的二進位制位元組流.

2、將類中的所有代表靜態資料的儲存結構轉化為方法區的執行時資料結構. **需要注意的:方法去也是堆,方法區中設定更多的是跟類相關的資料. 哪些資料跟類相關? 類的程式碼、靜態域/靜態屬性、靜態初始化、靜態方法、常量池(字串常量池).

3、在Java堆中生成一個代表這個類的java.lang.Class物件,作為對方法區中這些資料的訪問入口.

連結: 1、驗證 :驗證的作用,確保載入類的正確性. 驗證是連線階段的第一步,這一階段的目的是為了確保Class檔案的位元組流中包含的資訊符合當前虛擬機器的要求,並且不會危害虛擬機器自身的安全。驗證階段大致會完成4個階段的檢驗動作:

檔案格式驗證:驗證位元組流是否符合Class檔案格式的規範;例如:是否以0xCAFEBABE開頭、主次版本號是否在當前虛擬機器的處理範圍之內、常量池中的常量是否有不被支援的型別。

元資料驗證:對位元組碼描述的資訊進行語義分析(注意:對比javac編譯階段的語義分析),以保證其描述的資訊符合Java語言規範的要求;例如:這個類是否有父類,除了java.lang.Object 之外。

位元組碼驗證:通過資料流和控制流分析,確定程式語義是合法的、符合邏輯的。

符號引用驗證:確保解析動作能正確執行。

驗證階段是非常重要的,但不是必須的,它對程式執行期沒有影響,如果所引用的類經過反覆驗證,那麼可以考慮採用-Xverifynone引數來關閉大部分的類驗證措施,以縮短虛擬機器類載入的 時間。

2、準備 :負責給靜態屬性分配記憶體,並設定預設初始值.基本資料型別都是賦值為0,引用型別都為null. 準備階段是正式為類變數分配記憶體並設定類變數初始值的階段,這些記憶體都將在方法區中分配。對於該階段有以下幾點需要注意:

(1)、這時候進行記憶體分配的僅包括類變數(static),而不包括例項變數,例項變數會在物件例項化時隨著物件一塊分配在Java堆中。

(2)、這裡所設定的初始值通常情況下是資料型別預設的零值(如0、0L、null、false等),而不是被在Java程式碼中被顯式地賦予的值。

          假設一個類變數的定義為:public static int value = 3;

   那麼變數value在準備階段過後的初始值為0,而不是3,因為這時候尚未開始執行任何Java方法,而把value賦值為3的putstatic指令是在程式編譯後,存放於類構造器<clinit>()方法之 中的,所以把value賦值為3的動作將在初始化階段才會執行。

**這裡還需要注意如下幾點:

· 對基本資料型別來說,對於類變數(static)和全域性變數,如果不顯式地對其賦值而直接使用,則系統會為其賦予預設的零值,而對於區域性變數來說,在使用前必須顯式地為其賦值,否則 編譯時不通過。 · 對於同時被static和final修飾的常量,必須在宣告的時候就為其顯式地賦值,否則編譯時不通過;而只被final修飾的常量則既可以在宣告時顯式地為其賦值,也可以在類初始化時顯式地 為其賦值,總之,在使用前必須為其顯式地賦值,系統不會為其賦予預設零值。 · 對於引用資料型別reference來說,如陣列引用、物件引用等,如果沒有對其進行顯式地賦值而直接使用,系統都會為其賦予預設的零值,即null。 · 如果在陣列初始化時沒有對陣列中的各元素賦值,那麼其中的元素將根據對應的資料型別而被賦予預設的零值。

 (3)、如果類欄位的欄位屬性表中存在ConstantValue屬性,即同時被final和static修飾,那麼在準備階段變數value就會被初始化為ConstValue屬性所指定的值。

   假設上面的類變數value被定義為: public static final int value = 3;

   編譯時Javac將會為value生成ConstantValue屬性,在準備階段虛擬機器就會根據ConstantValue的設定將value賦值為3。回憶上一篇博文中物件被動引用的第2個例子,便是這種情況。我們 可以理解為static final常量在編譯期就將其結果放入了呼叫它的類的常量池中.

3、解析:把類中的符號引用轉換為直接引用

解析階段是虛擬機器將常量池內的符號引用替換為直接引用的過程,解析動作主要針對類或介面、欄位、類方法、介面方法、方法型別、方法控制代碼和呼叫點限定符7類符號引用進行。符號引用就 是一組符號來描述目標,可以是任何字面量。

直接引用就是直接指向目標的指標、相對偏移量或一個間接定位到目標的控制代碼。

初始化:初始化階段,就是JVM執行類的構造器 ()的階段,類構造器 ()是由編譯,器,自動收集類的所有類變數的賦值動作,和static語句塊的語句合併構成。

為類的靜態變數賦予正確的初始值, JVM負責對類進行初始化,主要對類變數進行初始化。

在Java中對類變數進行初始值設定有兩種方式: 1)宣告類變數是指定初始值  2)使用靜態程式碼塊為類變數指定初始值.

JVM初始化一個類的步驟. 1、假如這個類還沒有被載入和連線,則程式先載入並連線該類. 2、假如該類的直接父類還沒有被初始化,則先初始化其直接父類. 3、假如類中有初始化語句,則系統依次執行這些初始化語句.

類初始化的時機: 只有當對類的主動使用的時候才會導致類的初始化,類的主動引用包括以下六種: -建立類的例項,也就是new的方式 -訪問某個類或介面的靜態變數,或者對該靜態變數賦值 -呼叫類的靜態方法.反射(如class.forName( "com.shengsivuan.Test" )) 。 -初始化某個類的子類,則其父類也會被初始化. -Java虛擬機器啟動時被標明為啟動類的類(Java Test),直接使用iava.exe命令來執行某個主類.

但是類的被動引用,不會導致類的初始化,如以下幾種情況: -使用final修飾符的常量呼叫,不會初始化所在的類 -通過陣列定義類引用,也不會初始化所在的類 -通過子類引用父類的靜態變數,也不會導致子類初始化.

使用

解除安裝:在如下幾種情況下,Java虛擬機器將結束生命週期:

– 執行了System.exit()方法

– 程式正常執行結束

– 程式在執行過程中遇到了異常或錯誤而異常終止

– 由於作業系統出現錯誤而導致Java虛擬機器程序終止

類載入器

這幾種類載入器的層次關係如下圖所示:

                                 注意:這裡父類載入器並不是通過繼承關係來實現的,而是採用組合實現的。

在Java虛擬機器的角度來講,只存在兩種不同的類載入器:啟動類載入器:

1、它使用C++實現(這裡僅限於Hotspot,也就是JDK1.5之後預設的虛擬機器,有很多其他的虛擬機器是用Java語言實現的),是虛擬機器自身的一部分;

2、所有其他的類載入器:這些類載入器都由Java語言實現,獨立於虛擬機器之外,並且全部繼承自抽象類java.lang.ClassLoader,這些類載入器需要由啟動類載入器載入到記憶體中之後才能去 載入其他的類。

在Java開發人員的角度來看,類載入器可以大致劃分為以下三類: 1、啟動類載入器:Bootstrap ClassLoader,負責載入存放在JDK\jre\lib(JDK代表JDK的安裝目錄,下同)下,或被-Xbootclasspath引數指定的路徑中的,並且能被虛擬機器識別的類庫(如 rt.jar,所有的java.*開頭的類均被Bootstrap ClassLoader載入)。啟動類載入器是無法被Java程式直接引用的。

2、擴充套件類載入器:Extension ClassLoader,該載入器由sun.misc.Launcher$ExtClassLoader實現,它負責載入DK\jre\lib\ext目錄中,或者由java.ext.dirs系統變數指定的路徑中的所有類 庫(如javax.*開頭的類),開發者可以直接使用擴充套件類載入器。

3、應用程式類載入器:Application ClassLoader,該類載入器由sun.misc.Launcher$AppClassLoader來實現,它負責載入使用者類路徑(ClassPath)所指定的類,開發者可以直接使用該類加 載器,如果應用程式中沒有自定義過自己的類載入器,一般情況下這個就是程式中預設的類載入器。 1)在執行非置信程式碼之前,自動驗證數字簽名。

2)動態地建立符合使用者特定需要的定製化構建類。

3)從特定的場所取得java class,例如資料庫中和網路中。

類載入有三種方式:

1、命令列啟動應用時候由JVM初始化載入

2、通過Class.forName()方法動態載入

3、通過ClassLoader.loadClass()方法動態載入

Class.forName()和ClassLoader.loadClass()區別

Class.forName():將類的.class檔案載入到jvm中之外,還會對類進行解釋,執行類中的static塊;

ClassLoader.loadClass():只幹一件事情,就是將.class檔案載入到jvm中,不會執行static中的內容,只有在newInstance才會去執行static塊。

注:Class.forName(name, initialize, loader)帶參函式也可控制是否載入static塊。並且只有呼叫了newInstance()方法採用呼叫建構函式,建立類的物件 。

雙親委派模型

雙親委派模型的工作流程是:如果一個類載入器收到了類載入的請求,它首先不會自己去嘗試載入這個類,而是把請求委託給父載入器去完成,依次向上,因此,所有的類載入請求最終都應 該被傳遞到頂層的啟動類載入器中,只有當父載入器在它的搜尋範圍中沒有找到所需的類時,即無法完成該載入,子載入器才會嘗試自己去載入該類。

雙親委派機制: 1、當AppClassLoader載入一個class時,它首先不會自己去嘗試載入這個類,而是把類載入請求委派給父類載入器ExtClassLoader去完成。

2、當ExtClassLoader載入一個class時,它首先也不會自己去嘗試載入這個類,而是把類載入請求委派給BootStrapClassLoader去完成。

3、如果BootStrapClassLoader載入失敗(例如在$JAVA_HOME/jre/lib裡未查詢到該class),會使用ExtClassLoader來嘗試載入;

4、若ExtClassLoader也載入失敗,則會使用AppClassLoader來載入,如果AppClassLoader也載入失敗,則會報出異常ClassNotFoundException。

JVM類載入機制. 全盤負責:當一個類載入器負責載入某個class時,該Class所依賴的和引用的其他class也將由該類載入器負責載入,除非顯示使用另外一個類載入器來載入.

父類委託:先讓父類載入器試圖載入該類,只有在父類載入器無法載入該類時才嘗試從自己的類路徑中載入該類.

代理載入:當一個類載入器接受到一個類的載入請求的時候,讓其他的類載入器去幫助載入的過程,父類委託本身一種代理載入.

不同的類載入器,載入相同的類的時,產生的類的Class物件不一定一樣的.

    反射

使用反射可以獲得Java類中各個成員的名稱並顯示出來。簡單的說,反射就是讓你可以通過名稱來得到物件(類,屬性 ,方法的技術。

一句話:執行時探究和使用編譯時未知的類。

就是指的是,在Java中,可以在程式執行時期,動態的載入、探知和使用,在編譯期間,完全無法確定的類。就叫反射.

換句話說:Java在執行期間去載入一個只知道“類的全限定名”的類,並且獲取其完整的資料結構【執行是資料】,並且還可以參照這個完整的資料結構,我們擁有建立類的例項,以及訪問 類的屬性,訪問類的方法的能力,我們把這種能力,也稱為“自審”,“自省”,“內省”.

反射的核心:Class類.

獲得Class物件的方式主要有以下三種:

一:如果一個類的例項已經得到,你可以使用     Person p = new Person();     class c =p.getclass();             【Class c =物件名.getClass0:】   例: TextFieldt = new Jextfieldo;     Class c = t.getClass0;   Class s = c.getSuperclass0;

二:如果你在編譯期知道類的名字,你可以使用如下的方法    class c = Person.class; 【 Class c= JButton.class; 】 或者 Class c = Integer.TYPE 三:如果類名在編譯期不知道,但是在執行期可以獲得你可以使用下面的方法 【 Class c= Class.forName(stra): 】