java有兩種核心機(jī)制:java虛擬機(jī)(JavaVirtual Machine)與垃圾收集機(jī)制(Garbage collection):
1、Java虛擬機(jī):是運行所有Java程序的抽象計算機(jī),是Java語言的運行環(huán)境,在其上面運行Java代碼編譯后的字節(jié)碼程序,java虛擬機(jī)實現(xiàn)了平臺無關(guān)性。
2、Java垃圾回收(Garbage Collection):自動釋放不用對象內(nèi)存空間,在java程序運行過程中自動進(jìn)行,垃圾收集機(jī)制可大大縮短編程時間,保護(hù)程序的完整性,是Java語言安全性策略的一個重要部份。
java垃圾回收不需要程序員手動操作,我們經(jīng)常需要關(guān)注的是java虛擬機(jī),java虛擬機(jī)承載著程序從源碼到運行的全部工作。
Java虛擬機(jī)是可運行Java代碼的假想計算機(jī),有自己想象中的硬件,如處理器、堆棧、寄存器等,還具有相應(yīng)的指令系統(tǒng),可以執(zhí)行 Java 的字節(jié)碼程序。Java語言的一個非常重要的特點就是與平臺的無關(guān)性。而使用Java虛擬機(jī)是實現(xiàn)這一特點的關(guān)鍵。Java語言使用模式Java虛擬機(jī)屏蔽了與具體平臺相關(guān)的信息,使得Java語言編譯程序只需生成在Java虛擬機(jī)上運行的目標(biāo)代碼(字節(jié)碼),就可以在多種平臺上不加修改地運行。Java虛擬機(jī)在執(zhí)行字節(jié)碼時,把字節(jié)碼解釋成具體平臺上的機(jī)器指令執(zhí)行。
對于 JVM 的基本結(jié)構(gòu),我們可以從下圖可以大致了解:
從源文件創(chuàng)建到程序運行,Java程序要經(jīng)過兩大步驟:編譯,運行;1、源文件由編譯器編譯成字節(jié)碼(ByteCode) 2、字節(jié)碼由java虛擬機(jī)解釋運行。
1、 第一步(編譯): 創(chuàng)建完源文件之后,程序會被編譯器編譯為.class文件。Java編譯一個類時,如果這個類所依賴的類還沒有被編譯,編譯器就會先編譯這個被依賴的類,然后引用,否則直接引用。。編譯后的字節(jié)碼文件格式主要分為兩部分:常量池和方法字節(jié)碼。
2、第二步(運行):java類運行的過程大概可分為兩個過程:1、類的加載 2、執(zhí)行。
類加載過程
java程序經(jīng)過編譯后形成.class文件。通過類加載器將字節(jié)碼(.class)加載入JVM的內(nèi)存中。JVM將類加載過程分成加載,連接,初始化三個階段,其中連接階段又可分為驗證,準(zhǔn)備,解析三個階段。
JVM 的類加載是通過 ClassLoader 及其子類來完成的,類的層次關(guān)系和加載順序可以由下圖來描述:
1、Bootstrap ClassLoader啟動類加載器
負(fù)責(zé)加載$JAVA_HOME中jre/lib/里所有的 class(JDK 代表 JDK 的安裝目錄,下同),或被-Xbootclasspath參數(shù)指定的路徑中的,并且能被虛擬機(jī)識別的類庫(如 rt.jar,所有的java.*開頭的類均被 Bootstrap ClassLoader 加載)。啟動類加載器由 C++ 實現(xiàn),不是 ClassLoader 子類。無法被 Java 程序直接引用的。
2、Extension ClassLoader擴(kuò)展類加載器
該加載器由sun.misc.LauncherExtClassLoader實現(xiàn),負(fù)責(zé)加載Java平臺中擴(kuò)展功能的一些jar包,包括ExtClassLoader實現(xiàn),負(fù)責(zé)加載Java平臺中擴(kuò)展功能的一些jar包,包括JAVA_HOME中jre/lib/.jar或-Djava.ext.dirs指定目錄下的 jar 包。即JDKjrelibext目錄中,或者由 java.ext.dirs 系統(tǒng)變量指定的路徑中的所有類庫(如javax.開頭的類),開發(fā)者可以直接使用擴(kuò)展類加載器
3、App ClassLoader應(yīng)用程序類加載器
該類加載器由 sun.misc.Launcher$AppClassLoader 來實現(xiàn),負(fù)責(zé)記載 classpath 中指定的 jar 包及目錄中 class,開發(fā)者可以直接使用該類加載器,如果應(yīng)用程序中沒有自定義過自己的類加載器,一般情況下這個就是程序中默認(rèn)的類加載器。
啟動類加載器:它使用 C++ 實現(xiàn)(這里僅限于 Hotspot,也就是 JDK1.5 之后默認(rèn)的虛擬機(jī),有很多其他的虛擬機(jī)是用 Java 語言實現(xiàn)的),是虛擬機(jī)自身的一部分。
所有其他的類加載器:這些類加載器都由 Java 語言實現(xiàn),獨立于虛擬機(jī)之外,并且全部繼承自抽象類 java.lang.ClassLoader,這些類加載器需要由啟動類加載器加載到內(nèi)存中之后才能去加載其他的類。
應(yīng)用程序都是由這三種類加載器互相配合進(jìn)行加載的,我們還可以加入自定義的類加載器。
加載
加載時類加載過程的第一個階段,在加載階段,虛擬機(jī)需要完成以下三件事情:
1、通過一個類的全限定名來獲取其定義的二進(jìn)制字節(jié)流。
2、將這個字節(jié)流所代表的靜態(tài)存儲結(jié)構(gòu)轉(zhuǎn)化為方法區(qū)的運行時數(shù)據(jù)結(jié)構(gòu)。
3、在 Java 堆中生成一個代表這個類的 java.lang.Class 對象,作為對方法區(qū)中這些數(shù)據(jù)的訪問入口。
注意,這里第 1 條中的二進(jìn)制字節(jié)流并不只是單純地從 Class 文件中獲取,比如它還可以從 Jar 包中獲取、從網(wǎng)絡(luò)中獲?。ㄗ畹湫偷膽?yīng)用便是 Applet)、由其他文件生成(JSP 應(yīng)用)等。
相對于類加載的其他階段而言,加載階段(準(zhǔn)確地說,是加載階段獲取類的二進(jìn)制字節(jié)流的動作)是可控性最強(qiáng)的階段,因為開發(fā)人員既可以使用系統(tǒng)提供的類加載器來完成加載,也可以自定義自己的類加載器來完成加載。
(JVM主要在程序第一次主動使用類的時候,才會去加載該類。也就是說,JVM并不是在一開始就把一個程序就所有的類都加載到內(nèi)存中,而是到用的時候才把它加載進(jìn)來,而且只加載一次。)
加載過程中會先檢查類是否被已加載,檢查順序是自底向上,從 Custom ClassLoader 到 BootStrap ClassLoader 逐層檢查,只要某個 Classloader 已加載就視為已加載此類,保證此類只所有 ClassLoade r加載一次。而加載的順序是自頂向下,也就是由上層來逐層嘗試加載此類。
這幾種類加載器的層次關(guān)系如下圖所示:
這種層次關(guān)系稱為類加載器的雙親委派模型。雙親委派模型的工作流程是:
如果一個類加載器收到了類加載的請求,它首先不會自己去嘗試加載這個類,而是把請求委托給父加載器去完成,依次向上,因此,所有的類加載請求最終都應(yīng)該被傳遞到頂層的啟動類加載器中,只有當(dāng)父加載器在它的搜索范圍中沒有找到所需的類時,即無法完成該加載,子加載器才會嘗試自己去加載該類。
驗證
驗證的目的是為了確保 Class 文件中的字節(jié)流包含的信息符合當(dāng)前虛擬機(jī)的要求,而且不會危害虛擬機(jī)自身的安全。不同的虛擬機(jī)對類驗證的實現(xiàn)可能會有所不同,但大致都會完成以下四個階段的驗證:文件格式的驗證、元數(shù)據(jù)的驗證、字節(jié)碼驗證和符號引用驗證。
準(zhǔn)備
準(zhǔn)備階段是正式為類變量分配內(nèi)存并設(shè)置類變量初始值的階段,這些內(nèi)存都將在方法區(qū)中分配。對于該階段有以下幾點需要注意:
1、這時候進(jìn)行內(nèi)存分配的僅包括類變量(static),而不包括實例變量,實例變量會在對象實例化時隨著對象一塊分配在 Java 堆中。
2、這里所設(shè)置的初始值通常情況下是數(shù)據(jù)類型默認(rèn)的零值(如 0、0L、null、false 等),而不是被在 Java 代碼中被顯式地賦予的值。
解析
解析階段是虛擬機(jī)將常量池中的符號引用轉(zhuǎn)化為直接引用的過程。
解析動作主要針對類或接口、字段、類方法、接口方法四類符號引用進(jìn)行,分別對應(yīng)于常量池中的 CONSTANT_Class_info、CONSTANT_Fieldref_info、CONSTANT_Methodref_info、CONSTANT_InterfaceMethodref_info 四種常量類型。
1、類或接口的解析:判斷所要轉(zhuǎn)化成的直接引用是對數(shù)組類型,還是普通的對象類型的引用,從而進(jìn)行不同的解析。
2、字段解析:對字段進(jìn)行解析時,會先在本類中查找是否包含有簡單名稱和字段描述符都與目標(biāo)相匹配的字段,如果有,則查找結(jié)束;如果沒有,則會按照繼承關(guān)系從上往下遞歸搜索該類所實現(xiàn)的各個接口和它們的父接口,還沒有,則按照繼承關(guān)系從上往下遞歸搜索其父類,直至查找結(jié)束。
初始化
類初始化是類加載過程的最后一個階段,到初始化階段,才真正開始執(zhí)行類中的 Java 程序代碼。虛擬機(jī)規(guī)范嚴(yán)格規(guī)定了有且只有四種情況必須立即對類進(jìn)行初始化:
1、 遇到 new、getstatic、putstatic、invokestatic 這四條字節(jié)碼指令時,如果類還沒有進(jìn)行過初始化,則需要先觸發(fā)其初始化。生成這四條指令最常見的 Java 代碼場景是:使用 new 關(guān)鍵字實例化對象時、讀取或設(shè)置一個類的靜態(tài)字段(static)時(被 static 修飾又被 final 修飾的,已在編譯期把結(jié)果放入常量池的靜態(tài)字段除外)、以及調(diào)用一個類的靜態(tài)方法時。
2、 使用 Java.lang.refect 包的方法對類進(jìn)行反射調(diào)用時,如果類還沒有進(jìn)行過初始化,則需要先觸發(fā)其初始化。
3、當(dāng)初始化一個類的時候,如果發(fā)現(xiàn)其父類還沒有進(jìn)行初始化,則需要先觸發(fā)其父類的初始化。
4、當(dāng)虛擬機(jī)啟動時,用戶需要指定一個要執(zhí)行的主類,虛擬機(jī)會先執(zhí)行該主類。
虛擬機(jī)規(guī)定只有這四種情況才會觸發(fā)類的初始化,稱為對一個類進(jìn)行主動引用,除此之外所有引用類的方式都不會觸發(fā)其初始化,稱為被動引用。
Java初始化一個類的時候可以用new 操作符來初始化,也可通過Class.forName的方式來得到一個Class類型的實例,然后通過這個Class類型的實例的newInstance來初始化.我們把前者叫做JAVA的靜態(tài)加載,把后者叫做動態(tài)加載.。
有時候我們說某個語言具有很強(qiáng)的動態(tài)性,有時候我們會區(qū)分動態(tài)和靜態(tài)的不同技術(shù)與作法。我們朗朗上口動態(tài)綁定(dynamic binding)、動態(tài)鏈接(dynamic linking)、動態(tài)加載(dynamic loading)等。然而“動態(tài)”一詞其實沒有絕對而普遍適用的嚴(yán)格定義,有時候甚至像面向?qū)ο螽?dāng)初被導(dǎo)入編程領(lǐng)域一樣,一人一把號,各吹各的調(diào)。
一般而言,開發(fā)者社群說到動態(tài)語言,大致認(rèn)同的一個定義是:“程序運行時,允許改變程序結(jié)構(gòu)或變量類型,這種語言稱為動態(tài)語言”。從這個觀點看,Perl,Python,Ruby是動態(tài)語言,C++,Java,C#不是動態(tài)語言。
盡管在這樣的定義與分類下Java不是動態(tài)語言,它卻有著一個非常突出的動態(tài)相關(guān)機(jī)制:Reflection。Java程序可以加載一個運行時才得知名稱的class,獲悉其完整構(gòu)造(但不包括methods定義),并生成其對象實體、或?qū)ζ鋐ields設(shè)值、或喚起其methods。
1、靜態(tài)加載的時候如果在運行環(huán)境中找不到要初始化的類,拋出的是NoClassDefFoundError,它在JAVA的異常體系中是一個Error.
2、動態(tài)態(tài)加載的時候如果在運行環(huán)境中找不到要初始化的類,拋出的是ClassNotFoundException,它在JAVA的異常體系中是一個checked異常,在寫代碼的時候就需要catch.
JAVA反射機(jī)制:
在運行狀態(tài)中,對于任意一個類,都能夠知道這個類的所有屬性和方法;對于任意一個對象,都能夠調(diào)用它的任意一個方法和屬性;這種動態(tài)獲取的信息以及動態(tài)調(diào)用對象的方法的功能稱為java語言的反射機(jī)制。
Java反射機(jī)制主要提供了以下功能:
在運行時判斷任意一個對象所屬的類;在運行時構(gòu)造任意一個類的對象;在運行時判斷任意一個類所具有的成員變量和方法;在運行時調(diào)用任意一個對象的方法;生成動態(tài)代理。
Java有個Object 類,是所有Java 類的繼承根源,其內(nèi)聲明了數(shù)個應(yīng)該在所有Java 類中被改寫的方法:hashCode()、equals()、clone()、toString()、getClass()等。其中g(shù)etClass()返回一個Class 對象。
【Class 類十分特殊。它和一般類一樣繼承自O(shè)bject,其實體用以表達(dá)Java程序運行時的classes和interfaces,也用來表達(dá)enum、array、primitive Java types(boolean, byte, char, short, int, long, float, double)以及關(guān)鍵詞void。當(dāng)一個class被加載,或當(dāng)加載器(class loader)的defineClass()被JVM調(diào)用,JVM 便自動產(chǎn)生一個Class 對象?!?/p>
如果您想借由“修改Java標(biāo)準(zhǔn)庫源碼”來觀察Class 對象的實際生成時機(jī)(例如在Class的constructor內(nèi)添加一個println()),這樣是行不通的!因為Class并沒有public constructor。
Class是Reflection故事起源。針對任何您想探勘的類,唯有先為它產(chǎn)生一個Class 對象,接下來才能經(jīng)由后者喚起為數(shù)十多個的Reflection APIs。Reflection機(jī)制允許程序在正在執(zhí)行的過程中,利用Reflection APIs取得任何已知名稱的類的內(nèi)部信息,包括:package、 type parameters、 superclass、 implemented interfaces、 inner classes、 outer classes、 fields、 constructors、 methods、 modifiers等,并可以在執(zhí)行的過程中,動態(tài)生成instances、變更fields內(nèi)容或喚起methods。
從Class中獲取信息
Class類提供了大量的實例方法來獲取該Class對象所對應(yīng)的詳細(xì)信息,Class類大致包含如下方法,其中每個方法都包含多個重載版本,因此我們只是做簡單的介紹,詳細(xì)請參考JDK文檔
獲取類內(nèi)信息
一些判斷類本身信息的方法
(1)獲取class
主有三種獲得class的途徑,使用時要注意區(qū)別
1、 a,類型.class 如: String.class使用類名加“.class”的方式即會返回與該類對應(yīng)的Class對象。這個方法可以直接獲得與指定類關(guān)聯(lián)的Class對象,而并不需要有該類的對象存在。
2、 b,Class.forName('類名');該方法可以根據(jù)字符串參數(shù)所指定的類名獲取與該類關(guān)聯(lián)的Class對象。如果該類還沒有被裝入,該方法會將該類裝入JVM。forName方法的參數(shù)是類的完 整限定名(即包含包名)。通常用于在程序運行時根據(jù)類名動態(tài)的載入該類并獲得與之對應(yīng)的Class對象。
3、 c, obj.getClass();所有Java對象都具備這個方法,該方法用于返回調(diào)用該方法的對象的所屬類關(guān)聯(lián)的
(2)獲取構(gòu)造方法
Class類提供了四個public方法,用于獲取某個類的構(gòu)造方法:
1、Constructor getConstructor(Class[] params)根據(jù)構(gòu)造函數(shù)的參數(shù),返回一個具體的具有public屬性的構(gòu)造函數(shù)
2、Constructor getConstructors() 返回所有具有public屬性的構(gòu)造函數(shù)數(shù)組
3、Constructor getDeclaredConstructor(Class[] params) 根據(jù)構(gòu)造函數(shù)的參數(shù),返回一個具體的構(gòu)造函數(shù)(不分public和非public屬性)
4、Constructor getDeclaredConstructors() 返回該類中所有的構(gòu)造函數(shù)數(shù)組(不分public和非public屬性)
/
String className1='cn.testreflect.Worker';
Class clazz1=Class.forName(className1);
Object object1=clazz1.newInstance();
System.out.println('object1.toString()='+object1.toString());
/
*(4)獲取類的成員變量(成員屬性)
存在四種獲取成員屬性的方法
1、Field getField(String name) 根據(jù)變量名,返回一個具體的具有public屬性的成員變量
2、Field[] getFields() 返回具有public屬性的成員變量的數(shù)組
3、Field getDeclaredField(String name) 根據(jù)變量名,返回一個成員變量(不分public和非public屬性)
4、Field[] getDelcaredFields() 返回所有成員變量組成的數(shù)組(不分public和非public屬性)