Day18 (一)类的加载器
一個(gè)運(yùn)行時(shí)的Java虛擬機(jī)(JVM)負(fù)責(zé)運(yùn)行一個(gè)Java程序。
當(dāng)啟動(dòng)一個(gè)Java程序時(shí),一個(gè)虛擬機(jī)實(shí)例誕生;當(dāng)程序關(guān)閉退出,這個(gè)虛擬機(jī)實(shí)例也就隨之消亡。
如果在同一臺(tái)計(jì)算機(jī)上同時(shí)運(yùn)行多個(gè)Java程序,將得到多個(gè)Java虛擬機(jī)實(shí)例,每個(gè)Java程序都運(yùn)行于它自己的Java虛擬機(jī)實(shí)例中。
?
在如下幾種情況下,Java虛擬機(jī)將結(jié)束生命周期:
1.執(zhí)行了System.exit()方法
2.程序正常執(zhí)行結(jié)束
3.程序在執(zhí)行過程中遇到了異常或錯(cuò)誤而異常終止
4.由于操作系統(tǒng)出現(xiàn)錯(cuò)誤而導(dǎo)致Java虛擬機(jī)進(jìn)程終止
類的生命周期
類的加載
xx.class的字節(jié)碼文件,把字節(jié)碼文件加載到方法區(qū)中
類的加載指的是將類的.class文件中的二進(jìn)制數(shù)據(jù)讀入到內(nèi)存中,將其放在運(yùn)行時(shí)數(shù)據(jù)區(qū)的方法區(qū)內(nèi),然后在堆區(qū)創(chuàng)建一個(gè)java.lang.Class對象,用來封裝類在方法區(qū)內(nèi)的數(shù)據(jù)結(jié)構(gòu)。
加載.class文件的方式
1.從本地系統(tǒng)中直接加載
2.通過網(wǎng)絡(luò)下載.class文件
3.從zip,jar等歸檔文件中加載.class文件
4.從專有數(shù)據(jù)庫中提取.class文件
5.將Java源文件動(dòng)態(tài)編譯為.class文件
類的加載的最終產(chǎn)品是位于堆區(qū)中的Class對象。
Class對象封裝了類在方法區(qū)內(nèi)的數(shù)據(jù)結(jié)構(gòu),并且向Java程序員提供了訪問方法區(qū)內(nèi)的數(shù)據(jù)結(jié)構(gòu)的接口。
?
驗(yàn)證:驗(yàn)證字節(jié)碼文件的準(zhǔn)確性。
類的驗(yàn)證內(nèi)容:
1.類文件的結(jié)構(gòu)檢查
確保類文件遵從Java類文件的固定格式。
2.語義檢查
確保類本身符合Java語言的語法規(guī)定,比如驗(yàn)證final類型的類沒有子類,以及final類型的方法沒有被覆蓋。
注意,語義檢查的錯(cuò)誤在編譯器編譯階段就會(huì)通不過,但是如果有程序員通過非編譯的手段生成了類文件,其中有可能會(huì)含有語義錯(cuò)誤,此時(shí)的語義檢查主要是防止這種沒有編譯而生成的class文件引入的錯(cuò)誤。
3.字節(jié)碼驗(yàn)證
確保字節(jié)碼流可以被Java虛擬機(jī)安全地執(zhí)行。
字節(jié)碼流代表Java方法(包括靜態(tài)方法和實(shí)例方法),它是由被稱作操作碼的單字節(jié)指令組成的序列,每一個(gè)操作碼后都跟著一個(gè)或多個(gè)操作數(shù)。
字節(jié)碼驗(yàn)證步驟會(huì)檢查每個(gè)操作碼是否合法,即是否有著合法的操作數(shù)。
4.二級(jí)制兼容性的驗(yàn)證
確保相互引用的類之間的協(xié)調(diào)一致。
例如,在Worker類的gotoWork()方法中會(huì)調(diào)用Car類的run()方法,Java虛擬機(jī)在驗(yàn)證Worker類時(shí),會(huì)檢查在方法區(qū)內(nèi)是否存在Car類的run()方法,假如不存在(當(dāng)Worker類和Car類的版本不兼容就會(huì)出現(xiàn)這種問題),就會(huì)拋出NoSuchMethodError錯(cuò)誤。
準(zhǔn)備:給類變量(靜態(tài)變量)分配空間并且進(jìn)行默認(rèn)初始化。
在準(zhǔn)備階段,Java虛擬機(jī)為類的靜態(tài)變量分配內(nèi)存,并設(shè)置默認(rèn)的初始值。
例如對于以下Sample類,在準(zhǔn)備階段,將為int類型的靜態(tài)變量a分配4個(gè)字節(jié)的內(nèi)存空間,并且賦予默認(rèn)值0,為long類型的靜態(tài)變量b分配8個(gè)字節(jié)的內(nèi)存空間,并且賦予默認(rèn)值0。
public class Sample {private static int a = 1;private static long b;static {b = 2;} }解析:把字節(jié)碼中的一些符號(hào)轉(zhuǎn)換成指針
在解析階段,Java虛擬機(jī)會(huì)把類的二級(jí)制數(shù)據(jù)中的符號(hào)引用替換為直接引用。
例如在Worker類的gotoWork()方法中會(huì)引用Car類的run()方法。
public void gotoWork() {car.run();// 這段代碼在Worker類的二進(jìn)制數(shù)據(jù)中表示為符號(hào)引用 }在Worker類的二進(jìn)制數(shù)據(jù)中,包含了一個(gè)對Car類的run()方法的符號(hào)引用,它由run()方法的全名和相關(guān)描述符組成。
在解析階段,Java虛擬機(jī)會(huì)把這個(gè)符號(hào)引用替換為一個(gè)指針,該指針指向Car類的run()方法在方法區(qū)內(nèi)的內(nèi)存位置,這個(gè)指針就是直接引用。
類變量(靜態(tài)變量)進(jìn)行聲明處或靜態(tài)塊處初始化。
類的初始化步驟
1.假如這個(gè)類還沒有被加載和連接,那就先進(jìn)行加載和連接。
2.假如類存在直接的父類,并且這個(gè)父類還沒有被初始化,那就先初始化直接的父類。
3.假如類中存在初始化語句,那就依次執(zhí)行這些初始化語句。
類的初始化時(shí)機(jī)
Java程序?qū)︻惖氖褂梅绞娇梢苑譃閮煞N:
1.主動(dòng)使用
2.被動(dòng)使用
所有的Java虛擬機(jī)實(shí)現(xiàn)必須在每個(gè)類或接口被Java程序首次主動(dòng)使用時(shí)才初始化它們。
主動(dòng)使用的六種情況:
1.創(chuàng)建類的實(shí)例。
2.訪問某個(gè)類或接口的靜態(tài)變量,或者對該靜態(tài)變量賦值。
3.調(diào)用類的靜態(tài)方法
4.當(dāng)使用反射方法強(qiáng)制創(chuàng)建某個(gè)類或接口的對象時(shí)
5.當(dāng)初始化某個(gè)子類時(shí),該子類的所有父類都會(huì)被初始化
6.當(dāng)虛擬機(jī)Java命令運(yùn)行啟動(dòng)類
除了以上六種情況,其他使用Java類的方式都被看作是對類的被動(dòng)使用,都不會(huì)導(dǎo)致類的初始化。
調(diào)用類中的方法
卸載
類的加載器
Bootstrap、ClassLoader
負(fù)責(zé)加載核心類庫 lib下(C:\Program Files\Java\jdk1.8.0_151\jre\lib),是用原生代碼來實(shí)現(xiàn)的(C實(shí)現(xiàn)的),并不繼承自java.lang.ClassLoader。
加載擴(kuò)展類和應(yīng)用程序類加載器,并指定它們的父類加載器。
Extension ClassLoader,父類是根類加載器
用來加載java的擴(kuò)展庫(JAVA_HOME/jre/lib/ext/*.jar,或java.ext.dirs路徑下的內(nèi)容)java虛擬機(jī)的實(shí)現(xiàn)會(huì)提供一個(gè)擴(kuò)展庫目錄。該類加載器在此目錄里面查找并加載java類。
由sun.miscLauncher$ExtClassLoader實(shí)現(xiàn),繼承自java.lang.ClassLoader
System ClassLoader,父類是擴(kuò)展類加載器
負(fù)責(zé)加載自己編寫的類 classpath下
自定義類加載器
public static void main(String[] args) {//AppClassLoader 系統(tǒng)類加載器System.out.println(TestLoader1.class.getClassLoader());//ExtClassLoader 擴(kuò)張類加載器System.out.println(TestLoader1.class.getClassLoader().getParent());//null根類加載器無法查看System.out.println(TestLoader1.class.getClassLoader().getParent().getParent()); }
類加載的父委托機(jī)制
loader2首先從自己的命名空間中查找Sample類是否已經(jīng)被加載,如果已經(jīng)加載,就直接返回代表Sample類的Class對象的引用。
如果Sample類還沒有被加載,loader2首先請求loader1代為加載,loader1再請求系統(tǒng)類加載器代為加載,系統(tǒng)類加載器再請求擴(kuò)展類加載器代為加載,擴(kuò)展類加載器再請求根類加載器代為加載。
若根類加載器和擴(kuò)展類加載器都不能加載,則系統(tǒng)類加載器嘗試加載,若能加載成功,則將Sample類所對應(yīng)的Class對象的引用返回給loader1,loader1再返回給loader2,從而成功將Sample類加載進(jìn)虛擬機(jī)。
若系統(tǒng)加載器不能加載Sample類,則loader1嘗試加載Sample類,若loader1也不能成功加載,則loader2嘗試加載。
若所有的父加載器及l(fā)oader2本身都不能加載,則拋出ClassNotFoundException異常。
總結(jié)下來就是:
每個(gè)加載器都優(yōu)先嘗試用父類加載,若父類不能加載則自己嘗試加載;若成功則返回Class對象給子類,若失敗則告訴子類讓子類自己加載。所有都失敗則拋出異常。
類加載器的工作原理
類加載器的工作原理基于三個(gè)機(jī)制:委托、可見性和單一性。
委托機(jī)制
當(dāng)一個(gè)類加載和初始化的時(shí)候,類僅在有需要加載的時(shí)候被加載。假設(shè)你有一個(gè)應(yīng)用需要的類叫作Abc.class,首先加載這個(gè)類的請求由Application類加載器委托給它的父類加載器Extension類加載器,然后再委托給Bootstrap類加載器。Bootstrap類加載器會(huì)先看看rt.jar中有沒有這個(gè)類,因?yàn)椴]有這個(gè)類,所以這個(gè)請求由回到Extension類加載器,它會(huì)查看jre/lib/ext目錄下有沒有這個(gè)類,如果這個(gè)類被Extension類加載器找到了,那么它將被加載,而Application類加載器不會(huì)加載這個(gè)類;而如果這個(gè)類沒有被Extension類加載器找到,那么再由Application類加載器從classpath中尋找。記住classpath定義的是類文件的加載目錄,而PATH是定義的是可執(zhí)行程序如javac,java等的執(zhí)行路徑。
可見性機(jī)制
根據(jù)可見性機(jī)制,子類加載器可以看到父類加載器加載的類,而反之則不行。所以下面的例子中,當(dāng)Abc.class已經(jīng)被Application類加載器加載過了,然后如果想要使用Extension類加載器加載這個(gè)類,將會(huì)拋出java.lang.ClassNotFoundException異常。
package test;import java.util.logging.Level;import java.util.logging.Logger;/*** Java program to demonstrate How ClassLoader works in Java,* in particular about visibility principle of ClassLoader.** @author Javin Paul*/public class ClassLoaderTest {public static void main(String args[]) {try { //printing ClassLoader of this classSystem.out.println("ClassLoaderTest.getClass().getClassLoader() : "+ ClassLoaderTest.class.getClassLoader());//trying to explicitly load this class again using Extension class loaderClass.forName("test.ClassLoaderTest", true, ClassLoaderTest.class.getClassLoader().getParent());} catch (ClassNotFoundException ex) {Logger.getLogger(ClassLoaderTest.class.getName()).log(Level.SEVERE, null, ex);}}}輸出
ClassLoaderTest.getClass().getClassLoader() : sun.misc.Launcher$AppClassLoader@601bb1 16/08/2012 2:43:48 AM test.ClassLoaderTest main SEVERE: null java.lang.ClassNotFoundException: test.ClassLoaderTestat java.net.URLClassLoader$1.run(URLClassLoader.java:202)at java.security.AccessController.doPrivileged(Native Method)at java.net.URLClassLoader.findClass(URLClassLoader.java:190)at sun.misc.Launcher$ExtClassLoader.findClass(Launcher.java:229)at java.lang.ClassLoader.loadClass(ClassLoader.java:306)at java.lang.ClassLoader.loadClass(ClassLoader.java:247)at java.lang.Class.forName0(Native Method)at java.lang.Class.forName(Class.java:247)at test.ClassLoaderTest.main(ClassLoaderTest.java:29)單一性機(jī)制
根據(jù)這個(gè)機(jī)制,父加載器加載過的類不能被子加載器加載第二次。雖然重寫違反委托和單一性機(jī)制的類加載器是可能的,但這樣做并不可取。你寫自己的類加載器的時(shí)候應(yīng)該嚴(yán)格遵守這三條機(jī)制。
如何顯式的加載類
Java提供了顯式加載類的API:Class.forName(classname)和Class.forName(classname, initialized, classloader)。就像上面的例子中,你可以指定類加載器的名稱以及要加載的類的名稱。類的加載是通過調(diào)用java.lang.ClassLoader的loadClass()方法,而loadClass()方法則調(diào)用了findClass()方法來定位相應(yīng)類的字節(jié)碼。在這個(gè)例子中Extension類加載器使用了java.net.URLClassLoader,它從JAR和目錄中進(jìn)行查找類文件,所有以”/”結(jié)尾的查找路徑被認(rèn)為是目錄。如果findClass()沒有找到那么它會(huì)拋出java.lang.ClassNotFoundException異常,而如果找到的話則會(huì)調(diào)用defineClass()將字節(jié)碼轉(zhuǎn)化成類實(shí)例,然后返回。
什么地方使用類加載器
類加載器是個(gè)很強(qiáng)大的概念,很多地方被運(yùn)用。最經(jīng)典的例子就是AppletClassLoader,它被用來加載Applet使用的類,而Applets大部分是在網(wǎng)上使用,而非本地的操作系統(tǒng)使用。使用不同的類加載器,你可以從不同的源地址加載同一個(gè)類,它們被視為不同的類。J2EE使用多個(gè)類加載器加載不同地方的類,例如WAR文件由Web-app類加載器加載,而EJB-JAR中的類由另外的類加載器加載。有些服務(wù)器也支持熱部署,這也由類加載器實(shí)現(xiàn)。你也可以使用類加載器來加載數(shù)據(jù)庫或者其他持久層的數(shù)據(jù)。
?
總結(jié)
以上是生活随笔為你收集整理的Day18 (一)类的加载器的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Factom(公证通)--基于区块链的存
- 下一篇: windows server 2008解