摘要:本文主要講了Java類加載的機(jī)制,這是學(xué)習(xí)反射的入門基礎(chǔ)。
JVM和類
當(dāng)我們調(diào)用Java命令運行某個Java程序時,該命令將會啟動1條Java虛擬機(jī)進(jìn)程,不管該Java程序有多么復(fù)雜,該程序啟動了多少個線程,它們都處于該Java虛擬機(jī)進(jìn)程里。正如前面介紹的,同1個JVM的所有線程、所有變量都處于同1個進(jìn)程里,它們都使用該JVM進(jìn)程的內(nèi)存區(qū)。當(dāng)系統(tǒng)出現(xiàn)以下幾種情況時,JVM進(jìn)程將被終止:
1、程序運行到最后正常結(jié)束。
2、程序運行到使用System.exit()或Runtime.getRuntime().exit()代碼結(jié)束程序。
3、程序履行進(jìn)程中遇到未捕獲的異常或毛病而結(jié)束。
3、程序所在平臺強(qiáng)迫結(jié)束了JVM進(jìn)程。
從上面的介紹可以看出,當(dāng)Java程序運行結(jié)束時,JVM進(jìn)程結(jié)束,該進(jìn)程在內(nèi)存中狀態(tài)將會丟失。
類的生命周期
類的加載/類初始化
當(dāng)程序主動使用某個類時,如果該類還未被加載到內(nèi)存中,系統(tǒng)會通過加載、連接、初始化3個步驟來對該類進(jìn)行初始化,如果沒成心外,JVM將會連續(xù)完成這3個步驟,所以有時也把這3個步驟統(tǒng)稱為類加載或類初始化。
加載:查找并加載類的2進(jìn)制數(shù)據(jù)
1、通過1個類的全限定名來獲得定義此類的2進(jìn)制字節(jié)流。
2、將這個字節(jié)流所代表的靜態(tài)存儲結(jié)構(gòu)轉(zhuǎn)化為方法區(qū)的運行時數(shù)據(jù)結(jié)構(gòu)。
3、在java堆中生成1個代表這個類的java.lang.Class對象,作為方法區(qū)這些數(shù)據(jù)的訪問入口。
注意:將編譯后的java類文件(也就是.class文件)中的2進(jìn)制數(shù)據(jù)讀入內(nèi)存,并將其放在運行時數(shù)據(jù)區(qū)的方法區(qū)內(nèi),然后再堆區(qū)創(chuàng)建1個Java.lang.Class對象,用來封裝類在方法區(qū)的數(shù)據(jù)結(jié)構(gòu)。即加載后終究得到的是Class對象,并且更加值得注意的是:該Java.lang.Class對象是單實例的,不管這個類創(chuàng)建了多少個對象,他的Class對象時唯1的!
連接:
1、驗證:確保被加載的類的正確性
2、準(zhǔn)備:為類的靜態(tài)變量分配內(nèi)存,并將其初始化為默許值
3、解析:把類中的符號援用轉(zhuǎn)換為直接援用。
初始化:為類的靜態(tài)變量賦予正確的初始值。
注意:連接和初始化階段,其實靜態(tài)變量經(jīng)過了兩次賦值:第1次是靜態(tài)變量類型的默許值;第2次是我們真正賦給靜態(tài)變量的值。
我簡單畫了個圖,其進(jìn)程以下:
類加載指的是將類的class文件讀入內(nèi)存,并為之創(chuàng)建1個java.lang.Class對象,也就是說當(dāng)程序中使用任何類時,系統(tǒng)都會為之建立1個java.lang.Class對象。事實上,每一個類是1批具有相同特點的對象的抽象(或說概念),而系統(tǒng)中所有的類,它們實際上也是對象,它們都是java.lang.Class的實例。
加載由類加載器完成,類加載器通常由JVM提供,這些類加載器也是我們前面所有程序運行的基礎(chǔ),JVM提供的這些類加載器通常被稱為系統(tǒng)類加載器。除此以外,開發(fā)者可以通過繼承ClassLoader基類來創(chuàng)建自己的類加載器。
通過使用不同的類加載器,可以從不同來源加載類的2進(jìn)制數(shù)據(jù),通常有以下幾種來源:
1、從本地文件系統(tǒng)來加載class文件,這是絕大部份示例程序的類加載方式。
2、從JAR包中加載class文件,這類方式也是很常見的,前面介紹JDBC編程時用到的數(shù)據(jù)庫驅(qū)動類就是放在JAR文件中,JVM可以從JAR文件中直接加載該class文件。
3、通過網(wǎng)絡(luò)加載class文件。
4、把1個Java源文件動態(tài)編譯、并履行加載。
類加載器通常不必等到“首次使用”該類時才加載該類,Java虛擬機(jī)規(guī)范允許系統(tǒng)預(yù)先加載某些類。
Java程序?qū)︻惖氖褂梅绞?br />
主動使用
1、創(chuàng)建類的實例
2、方法某個類或接口的靜態(tài)變量,或?qū)υ撿o態(tài)變量賦值
3、調(diào)用類的靜態(tài)方法
4、反射(如 Class.forName(“com.itzhai.Test”))
5、初始化1個類的子類
6、Java虛擬機(jī)啟動時被標(biāo)明為啟動類的類(Main Class)
被動使用
除以上6中方式,其他對類的使用都是被動使用,都不會致使類的初始化。類的初始化時機(jī)正是java程序?qū)︻惖氖状沃鲃邮褂茫?br />
所有的Java虛擬機(jī)實現(xiàn)必須在每一個類或接口被Java程序“首次主動使用”時才初始化它們。
對象初始化
在類被裝載、連接和初始化,這個類就隨時都可能使用了。對象實例化和初始化是就是對象生命的起始階段的活動,在這里我們主要討論對象的初始化工作的相干特點。
Java 編譯器在編譯每一個類時都會為該類最少生成1個實例初始化方法--即"()" 方法。此方法與源代碼中的每一個構(gòu)造方法相對應(yīng),如果類沒有明確地聲明任何構(gòu)造方法,編譯器則為該類生成1個默許的無參構(gòu)造方法,這個默許的構(gòu)造器僅僅調(diào)用父類的無參構(gòu)造器,與此同時也會生成1個與默許構(gòu)造方法對應(yīng)的 "()" 方法.
通常來講,() 方法內(nèi)包括的代碼內(nèi)容大概為:調(diào)用另外一個() 方法;對實例變量初始化;與其對應(yīng)的構(gòu)造方法內(nèi)的代碼。
如果構(gòu)造方法是明確地從調(diào)用同1個類中的另外一個構(gòu)造方法開始,那它對應(yīng)的() 方法體內(nèi)包括的內(nèi)容為:1個對本類的() 方法的調(diào)用;對利用構(gòu)造方法內(nèi)的所有字節(jié)碼。
如果構(gòu)造方法不是通過調(diào)用本身類的其它構(gòu)造方法開始,并且該對象不是 Object 對象,那() 法內(nèi)則包括的內(nèi)容為:1個對父類() 方法的調(diào)用;對實例變量初始化方法的字節(jié)碼;最后是對應(yīng)構(gòu)造子的方法體字節(jié)碼。
如果這個類是 Object,那末它的() 方法則不包括對父類() 方法的調(diào)用。
1、相同點:
通過這幾種方式,得到的都是Java.lang.Class對象(這個是上面講到的類在加載時取得的終究產(chǎn)物)
例如:
結(jié)果:
2、區(qū)分:
下面用1個實例來講說它們的區(qū)分
以下新建1個類
然后開始使用:
如果,將Class.forName()去掉:
以下:
結(jié)果又變成:
所以,可以得出以下結(jié)論:
1)Class cl=Cat.class; JVM將使用類Cat的類裝載器,將類A裝入內(nèi)存(條件是:類A還沒有裝入內(nèi)存),不對類A做類的初始化工作.返回類A的Class的對象
2)Class cl=對象援用o.getClass();返回援用o運行時真正所指的對象(由于:兒子對象的援用可能會賦給父對象的援用變量中)所屬的類的Class的對象 ,如果還沒裝載過,會進(jìn)行裝載。
3)Class.forName("類名"); 裝入類A,并做類的初始化(條件是:類A還沒有裝入內(nèi)存)
從JVM的角度看,我們使用關(guān)鍵字new創(chuàng)建1個類的時候,這個類可以沒有被加載。但是使用Class對象的newInstance()方法的時候,就必須保證:
1、這個類已加載;
2、這個類已連接了。而完成上面兩個步驟的正是Class的靜態(tài)方法forName()所完成的,這個靜態(tài)方法調(diào)用了啟動類加載器,即加載 java API的那個加載器。
現(xiàn)在可以看出,Class對象的newInstance()(這類用法和Java中的工廠模式有著異曲同工之妙)實際上是把new這個方式分解為兩步,即首先調(diào)用Class加載方法加載某個類,然后實例化。這樣分步的好處是不言而喻的。我們可以在調(diào)用class的靜態(tài)加載方法forName時取得更好的靈活性,提供給了1種降耦的手段。
Class.forName().newInstance()和通過new得到對象的區(qū)分
1、使用newInstance可以解耦。使用newInstance的條件是,類已加載并且這個類已連接,這是正是class的靜態(tài)方法forName()完成的工作。newInstance實際上是把new 這個方式分解為兩步,即,首先調(diào)用class的加載方法加載某個類,然后實例化。
2、newInstance: 弱類型。低效力。只能調(diào)用無參構(gòu)造。 new: 強(qiáng)類型。相對高效。能調(diào)用任何public構(gòu)造。
3、newInstance()是實現(xiàn)IOC、反射、面對接口編程和依賴顛倒等技術(shù)方法的必定選擇,new只能實現(xiàn)具體類的實例化,不合適于接口編程。
4、 newInstance() 1般用于動態(tài)加載類。
5、Class.forName(“”).newInstance()返回的是object 。
6、newInstance( )是1個方法,而new是1個關(guān)鍵字;
注:1般在通用框架里面用的就是class.forName來加載類,然后再通過反射來調(diào)用其中的方法,比方Tomcat源碼里面,這樣就避免了new關(guān)鍵字的耦合度,還有讓不同的類加載器來加載不同的類,方便提高類之間的安全性和隔離性.