search
尋找貓咪~QQ 地點 桃園市桃園區 Taoyuan , Taoyuan

深入理解Java:類載入機制及反射

一、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的同學注意了!!!

學習過程中遇到什麼問題或者想獲取學習資源的話,歡迎加入Java學習交流群,群號碼:392216227我們一起學Java!



熱門推薦

本文由 yidianzixun 提供 原文連結

寵物協尋 相信 終究能找到回家的路
寫了7763篇文章,獲得2次喜歡
留言回覆
回覆
精彩推薦