1. 程式人生 > >深入理解Java:類載入機制及反射

深入理解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屬性)

 參考:

《深入理解JVM虛擬機器》

相關推薦

深入理解Java載入機制反射

 說明:本文乃學習整理參考而來. 一、Java類載入機制 1.概述        Class檔案由類裝載器裝載後,在JVM中將形成一份描述Class結構的元資訊物件,通過該元資訊物件可以獲知Class的結構資訊:如建構函式,屬性和方法等,Java允許使用者藉由這個Class相關的元資訊物件間接呼

深入理解Java加載機制反射

指定 請求 image vm虛擬機 常量池 使用 元素 靜態 創建 一、Java類加載機制 1.概述 Class文件由類裝載器裝載後,在JVM中將形成一份描述Class結構的元信息對象,通過該元信息對象可以獲知Class的結構信息:如構造函數,屬性和方法等,J

深入探討Java載入機制

 Java 語言是一種具有動態性的解釋型程式語言,當指定程式執行的時候, Java 虛擬機器就將編譯生成的 . class 檔案按照需求和一定的規則載入進記憶體,並組織成為一個完整的 Java 應用程式。 Java 語言把每個單獨的類 Class 和介面 Implements

深入理解jvm的載入機制

一、類的生命週期 java程式使用某個類時,必須按照以下順序執行: (1)載入:查詢並載入類的二進位制資料; (2)連線:包括驗證、準備和解析類的二進位制; 驗證:確保載入類的正確性;準備:為類的靜態變數分配記憶體,並將其初始化為預設值;解析:把類中的符號引用轉為直接引用(3)初始化:給類的靜態變數賦

[JVM]載入機制反射

一、Java類載入機制 1.概述        Class檔案由類裝載器裝載後,在JVM中將形成一份描述Class結構的元資訊物件,通過該元資訊物件可以獲知Class的結構資訊:如建構函式,屬性和方法等,Java允許使用者藉由這個Class相關的元資訊物件間接呼叫Clas

深入理解JAVA虛擬機器6載入機制

類載入機制 虛擬機器把描述類的資料從Class檔案載入到記憶體,並對資料進行校驗、轉換解析和初始化,最終形成可以被虛擬機器直接使用的 Java 型別,這就是虛擬機器類載入機制。(類是在執行期間動態載入的) 懶載入:要用的時候再去載入。舉個栗子,我們的電腦上有很多軟體,比如

深入理解JVM】載入機制

概述 虛擬機器把描述類的資料從Class檔案載入到記憶體,並對資料進行校驗、轉換解析和初始化,最終形成可以被虛擬機器直接使用的Java型別,這就是虛擬機器的類載入機制。 與那些在編譯時需要進行連結工作的語言不同,在Java語言裡,型別的載入、連線和初始化過程

深入Java虛擬機器】之三載入機制

類載入過程     類從被載入到虛擬機器記憶體中開始,到卸載出記憶體為止,它的整個生命週期包括:載入、驗證、準備、解析、初始化、使用和解除安裝七個階段。它們開始的順序如下圖所示:     其中類載入的過程包括了載入、驗證、準備、

深入Java虛擬機器】之四載入機制

    我們來看得到該結果的步驟。首先在準備階段為類變數分配記憶體並設定類變數初始值,這樣A和B均被賦值為預設值0,而後再在呼叫<clinit>()方法時給他們賦予程式中指定的值。當我們呼叫Child.b時,觸發Child的<clinit>()方法,根據規則2,在此之前,要先執行完其父

Java虛擬機器載入機制詳解

    大家知道,我們的Java程式被編譯器編譯成class檔案,在class檔案中描述的各種資訊,最終都需要載入到虛擬機器記憶體才能執行和使用,那麼虛擬機器是如何載入這些class檔案的呢?在載入class檔案的過程中虛擬機器又幹了哪些事呢?今天我們來解密虛擬機器的類載入機制。

深入理解java執行緒本地變數 java.lang.ThreadLocal

ThreadLocal,很多人都叫它做執行緒本地變數,也有些地方叫做執行緒本地儲存,其實意思差不多。 可能很多朋友都知道ThreadLocal為變數在每個執行緒中都建立了一個副本,那樣每個執行緒可以訪問自己內部的副本變數。 這句話從表面上看起來理解正確,但實際上這種理解是不太正確的。下面我們

《瘋狂Java講義》讀書筆記(十)多執行緒,網路程式設計,載入機制反射

第十六章:多執行緒1、一般而言,程序包含如下3個特徵:獨立性,動態性,併發性。併發性和並行性是兩個概念,並行指同一時刻,有多條指令在多個處理器上同時執行;併發指同一時刻只能有一條指令執行,但多個程序指令

深入理解Java註解(Annotation)--註解處理器

fault this urn 復制代碼 lena ide set java lec 深入理解Java:註解(Annotation)--註解處理器   如果沒有用來讀取註解的方法和工作,那麽註解也就不會比註釋更有用處了。使用註解的過程中,很重要的一部分就是創建於

深入理解Java註解

註釋 element 每一個 gree arc res 參數名稱 生命周期 水果 註解作用:每當你創建描述符性質的類或者接口時,一旦其中包含重復性的工作,就可以考慮使用註解來簡化與自動化該過程。 Java提供了四種元註解,專門負責新註解的創建工作。‘ 元註解

深入理解spring的事務管理機制程式碼實現

Spring的事務管理機制 Spring事務管理高層抽象主要包括3個介面,Spring的事務主要是由他們共同完成的: PlatformTransactionManager:事務管理器—主要用於平臺相關事務的管理 TransactionDefinition: 事務定義資訊(隔

分散式系統的架構思路 深入理解java5. Java分散式架構

原文來源:https://www.cnblogs.com/chulung/p/5653135.html 一、前言 在計算機領域,當單機效能達到瓶頸時,有兩種方式可以解決效能問題,一是堆硬體,進一步提升配置,二是分散式,水平擴充套件。當然,兩者都是一樣的燒錢。今天聊聊我所理解的分散式系統的架構思路。

深入理解Java虛擬機器--垃圾收集故障診斷

1.垃圾收集演算法     1.1 標記-清除演算法           演算法分為標記和清除兩個階段:首先標記出所有需要回收的物件,在標記完成後統一回收所有被標記的物件,標記過程上一篇部落格說過, 後續的幾種演算

深入理解java抽象

抽象類概念 在面向物件的概念中,所有的物件都是通過類來描述的,但是並不是所有的類都描述了物件,有些類裡面並沒有包含足夠的資訊來描述物件,這些類被認為是抽象類。 抽象類與普通類的區別就在於抽象類不能被例項化,這就決定了抽象類必須有子類實現它的抽象方法 定義與使用

深入理解Java型別資訊(Class物件)與反射機制

關聯文章: 本篇主要是深入對Java中的Class物件進行分析,這對後續深入理解反射技術非常重要,主要內容如下: 深入理解Class物件 RRTI的概念以及Class物件作用 認識Class物件之前,先來了解一個概念,RTTI(Run-Time

深入理解JavaSimpleDateFormat安全的時間格式化

  想必大家對SimpleDateFormat並不陌生。SimpleDateFormat 是 Java 中一個非常常用的類,該類用來對日期字串進行解析和格式化輸出,但如果使用不小心會導致非常微妙和難以除錯的問題,因為 DateFormat 和 SimpleDateForm