說明:本文乃學習整理參考而來.html
1.概述java
Class文件由類裝載器裝載後,在JVM中將造成一份描述Class結構的元信息對象,經過該元信息對象能夠獲知Class的結構信息:如構造函數,屬性和方法等,Java容許用戶藉由這個Class相關的元信息對象間接調用Class對象的功能。git
虛擬機把描述類的數據從class文件加載到內存,並對數據進行校驗,轉換解析和初始化,最終造成能夠被虛擬機直接使用的Java類型,這就是虛擬機的類加載機制。github
2.工做機制數組
類裝載器就是尋找類的字節碼文件,並構造出類在JVM內部表示的對象組件。在Java中,類裝載器把一個類裝入JVM中,要通過如下步驟:數據結構
(1) 裝載:查找和導入Class文件;函數
(2) 連接:把類的二進制數據合併到JRE中;工具
(a)校驗:檢查載入Class文件數據的正確性;post
(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。
一、獲取構造方法
Class類提供了四個public方法,用於獲取某個類的構造方法。
Constructor getConstructor(Class[] params)
根據構造函數的參數,返回一個具體的具備public屬性的構造函數
Constructor getConstructors()
返回全部具備public屬性的構造函數數組
Constructor getDeclaredConstructor(Class[] params)
根據構造函數的參數,返回一個具體的構造函數(不分public和非public屬性)
Constructor getDeclaredConstructors()
返回該類中全部的構造函數數組(不分public和非public屬性)
二、獲取類的成員方法
與獲取構造方法的方式相同,存在四種獲取成員方法的方式。
Method getMethod(String name, Class[] params)
根據方法名和參數,返回一個具體的具備public屬性的方法
Method[] getMethods()
返回全部具備public屬性的方法數組
Method getDeclaredMethod(String name, Class[] params)
根據方法名和參數,返回一個具體的方法(不分public和非public屬性)
Method[] getDeclaredMethods()
返回該類中的全部的方法數組(不分public和非public屬性)
三、獲取類的成員變量(成員屬性)
存在四種獲取成員屬性的方法
Field getField(String name)
根據變量名,返回一個具體的具備public屬性的成員變量
Field[] getFields()
返回具備public屬性的成員變量的數組
Field getDeclaredField(String name)
根據變量名,返回一個成員變量(不分public和非public屬性)
Field[] getDelcaredFields()
返回全部成員變量組成的數組(不分public和非public屬性)
參考:
《深刻理解JVM虛擬機》