java class load 类加载
1:what is ?it
jvm把描述類的數(shù)據(jù)從class字節(jié)碼文件加載到內(nèi)存,并對數(shù)據(jù)進行校驗、解析、初始化,最終成為jvm直接使用的數(shù)據(jù)類型
?1、ClassNotFoundExcetpion?
我們在開發(fā)中,經(jīng)常可以遇見java.lang.ClassNotFoundExcetpion這個異常,今天我就來總結(jié)一下這個問題。對于這個異常,它實質(zhì)涉及到了java技術(shù)體系中的類加載。Java的類加載機制是技術(shù)體系中比較核心的部分,雖然它和我們直接打交道不多,但是對其背后的機理有一定理解有助于我們排查程序中出現(xiàn)的類加載失敗等技術(shù)問題。?
2、類的加載過程?
一個java文件從被加載到被卸載這個生命過程,總共要經(jīng)歷5個階段,JVM將類加載過程分為:?
加載->鏈接(驗證+準(zhǔn)備+解析)->初始化(使用前的準(zhǔn)備)->使用->卸載?
(1)加載?
首先通過一個類的全限定名來獲取此類的二進制字節(jié)流;其次將這個字節(jié)流所代表的靜態(tài)存儲結(jié)構(gòu)轉(zhuǎn)化為方法區(qū)的運行時數(shù)據(jù)結(jié)構(gòu);最后在java堆中生成一個代表這個類的Class對象,作為方法區(qū)這些數(shù)據(jù)的訪問入口。總的來說就是查找并加載類的二進制數(shù)據(jù)。?
(2)鏈接:?
驗證:確保被加載類的正確性;?
準(zhǔn)備:為類的靜態(tài)變量分配內(nèi)存,并將其初始化為默認(rèn)值;?
解析:把類中的符號引用轉(zhuǎn)換為直接引用;?
(3)為類的靜態(tài)變量賦予正確的初始值?
3、類的初始化?
(1)類什么時候才被初始化?
1)創(chuàng)建類的實例,也就是new一個對象?
2)訪問某個類或接口的靜態(tài)變量,或者對該靜態(tài)變量賦值?
3)調(diào)用類的靜態(tài)方法?
4)反射(Class.forName(“com.lyj.load”))?
5)初始化一個類的子類(會首先初始化子類的父類)?
6)JVM啟動時標(biāo)明的啟動類,即文件名和類名相同的那個類?
(2)類的初始化順序?
1)如果這個類還沒有被加載和鏈接,那先進行加載和鏈接?
2)假如這個類存在直接父類,并且這個類還沒有被初始化(注意:在一個類加載器中,類只能初始化一次),那就初始化直接的父類(不適用于接口)?
3)加入類中存在初始化語句(如static變量和static塊),那就依次執(zhí)行這些初始化語句。?
4)總的來說,初始化順序依次是:(靜態(tài)變量、靜態(tài)初始化塊)–>(變量、初始化塊)–> 構(gòu)造器;如果有父類,則順序是:父類static方法 –> 子類static方法 –> 父類構(gòu)造方法- -> 子類構(gòu)造方法?
2:when it happens
初始化時機??Jvm規(guī)范規(guī)定了?有且僅有5種
1new getstatic putstatic invokestatic 4條指令時 (new一個對象 使用靜態(tài)屬性和方法)
2使用java.lang.reflect包的方法對某個類進行反射調(diào)用 forname
3 初始化一個類時發(fā)現(xiàn)其父類尚未初始化 先初始化父類
4Jvm啟動時? 加載main方法所在的類
5Jdk1.7的動態(tài)語言 MathodHander解析結(jié)果為 REF_getStatic REF_putStatic REF_inpokStatic 加載對應(yīng)類
?
3:ClassLoader
類加載器 實現(xiàn)類加載的動作的類
3.1jvm中的類加載器:
1.引導(dǎo)類(Bootstrap classloader):組成Java平臺的類,包括jre/lib/rt.jar -Xbootclasspath指定? 按文件名識別
2.擴展類(Extensions classloader):使用Java擴展機制的類,jre/lib/ext? java.ext.dirs指定的其他jar
3.用戶類(application classloader 系統(tǒng)、應(yīng)用類加載器):
由開發(fā)者定義的類和沒有利用擴展機制的第三方類,這些類的位置由用戶指定,
一般通過使用-classpath命令行選項或者使用CLASSPATH環(huán)境變量來指定其位置。
?3.2層次結(jié)構(gòu):
Bootstrap > Extension > Application > user classloder
但他們之間不是以繼承的關(guān)系來實現(xiàn) 而是組合的形式來復(fù)用父加載器的方法
每個累加器都有parent屬性指向它的上級類加器(見下面代碼)
?
3.3雙親委派模型(見下圖)
????? 若一個類加載器收到類加載的請求 他首先自己不會嘗試加載這個類 而是把這個請求委托給父類加器去完成
????? 每一個類加器都如此 這樣所有的類加載請求都會傳入到bootstrap中 只有當(dāng)父加載器加載不了時
? ? ?此時父加載器拋出異常,子加載器捕獲后 再在自己的領(lǐng)域內(nèi)嘗試加載
?
比如:
* 代碼中出現(xiàn)了這么一行:new A();
> 系統(tǒng)發(fā)現(xiàn)了自己加載的類,其中包含了new A(),這說明需要系統(tǒng)去加載A類
> 系統(tǒng)會給自己的領(lǐng)導(dǎo)打電話:讓擴展去自己的地盤去加載A類
> 擴展會給自己的領(lǐng)導(dǎo)打電話:讓引導(dǎo)去自己的地盤去加載A類
> 引導(dǎo)自己真的去rt.jar中尋找A類
? ?* 如果找到了,那么加載之,然后返回A對應(yīng)的Class對象給擴展,擴展也會它這個Class返回給系統(tǒng),結(jié)束了!
? ?* 如果沒找到:
? ? ? ?> 引導(dǎo)給擴展返回了一個null,擴展會自己去自己的地盤,去尋找A類
? ? ? ? ? * 如果找到了,那么加載之,然后返回A對應(yīng)的Class對象給系統(tǒng),結(jié)束了!
? ? ? ? ? * 如果沒找到
? ? ? ? ? ? ? > 擴展返回一個null給系統(tǒng)了,系統(tǒng)去自己的地盤(應(yīng)用程序下)加載A類
? ? ? ? ? ? ? ? ? * 如果找到了,那么加載之,然后返回這個Class,結(jié)束了!
? ? ? ? ? ? ? ? ? * 如果沒找到,拋出異常ClassNotFoundException
?
好處:java類隨著他的類加載器一起具備了一種帶有優(yōu)先級的層次關(guān)系 如java.lang,Object類放在rt.jar內(nèi)
無論哪一個類加載器要加載它,最終都要委托到啟動類加載器加載他 因此 在各種類加載器環(huán)境中使用Object都是同一個類
如果沒有這種委派機制 用 戶自定義一個java.lang.Object放在classpath下
加載時系統(tǒng)將會出現(xiàn)多個不同的object類 Java類型體系的最基礎(chǔ)的行為就會被破壞掉 程序也將一片混亂
?
同一個類: 只有2個類由同一個類加載器加載的前提下 他們才可能相等
即 相等條件: 相同的class文件 + 同一個類加載器加載
?
?3.5core code
1 // The parent class loader for delegation 2 private ClassLoader parent;?
1 protected synchronized Class<?> loadClass(String name, boolean resolve) 2 3 throws ClassNotFoundException 4 5 { 6 7 // First, check if the class has already been loaded 8 9 Class c = findLoadedClass(name); 10 11 if (c == null) { 12 13 try { 14 15 if (parent != null) { 16 17 c = parent.loadClass(name, false); 18 19 } else { 20 21 c = findBootstrapClassOrNull(name);//只有bootstrap會執(zhí)行這一句 22 23 } 24 25 } catch (ClassNotFoundException e) { 26 27 // ClassNotFoundException thrown if class not found 28 29 // from the non-null parent class loader 30 31 } 32 33 if (c == null) { 34 35 // If still not found, then invoke findClass in order 36 37 // to find the class. 38 39 c = findClass(name); 40 41 } 42 43 } 44 45 if (resolve) { 46 47 resolveClass(c); 48 49 } 50 51 return c; 52 53 }先判斷是否已經(jīng)加載 內(nèi)存中已有 不再加載
不存在 判斷parent加載器是否為null ? no 則調(diào)用父加載器的loadClass方法 否則
該類加載器為bootstrap 他執(zhí)行自己的加載方法findBootstrapClassOrNull
當(dāng)父類在自己的領(lǐng)域內(nèi)找不到時 findClass會拋出異常
子類捕獲異常后 就會嘗試在自己領(lǐng)域內(nèi)加載
3.6圖解
3.7自定義類加載器
我們也可以通過繼承ClassLoader類來完成自定義類加載器,自類加載器的目的一般是為了加載網(wǎng)絡(luò)上的類,因為這會讓class在網(wǎng)絡(luò)中傳輸,為了安全,那么class一定是需要加密的,所以需要自定義的類加載器來加載(自定義的類加載器需要做解密工作)。
ClassLoader加載類都是通過loadClass()方法來完成的,loadClass()方法的工作流程如下:
l? 調(diào)用findLoadedClass?()方法查看該類是否已經(jīng)被加載過了,如果該沒有加載過,那么這個方法返回null;
l? 判斷findLoadedClass()方法返回的是否為null,如果不是null那么直接返回,這可以避免同一個類被加載兩次;
l? 如果findLoadedClass()返回的是null,那么就啟動代理模式(委托機制),即調(diào)用上級的loadClass()方法,獲取上級的方法是getParent(),當(dāng)然上級可能還有上級,這個動作就一直向上走;
l? 如果getParent().loadClass()返回的不是null,這說明上級加載成功了,那么就加載結(jié)果;
l? 如果上級返回的是null,這說明需要自己出手了,這時loadClass()方法會調(diào)用本類的findClass()方法來加載類;
l? 這說明我們只需要重寫ClassLoader的findClass()方法,這就可以了!如果重寫了loadClass()方法覆蓋了代理模式!
?
OK,通過上面的分析,我們知道要自定義一個類加載器,只需要繼承ClassLoader類,然后重寫它的findClass()方法即可。那么在findClass()方法中我們要完成哪些工作呢?
l? 找到class文件,把它加載到一個byte[]中;
l? 調(diào)用defineClass()方法,把byte[]傳遞給這個方法即可。
1 public class FileSystemClassLoader extends ClassLoader { 2 private String classpath ; 3 4 public FileSystemClassLoader() {} 5 6 public FileSystemClassLoader (String classpath) { 7 this.classpath = classpath; 8 } 9 10 @Override 11 public Class<?> findClass(String name) throws ClassNotFoundException { 12 try { 13 byte[] datas = getClassData(name); 14 if(datas == null) { 15 throw new ClassNotFoundException("類沒有找到:" + name); 16 } 17 return this.defineClass (name, datas, 0, datas.length); 18 } catch (IOException e) { 19 e.printStackTrace(); 20 throw new ClassNotFoundException("類找不到:" + name); 21 } 22 } 23 24 private byte[] getClassData(String name) throws IOException { 25 name = name.replace(".", "\\") + ".class"; 26 File classFile = new File(classpath, name); 27 return FileUtils .readFileToByteArray(classFile); 28 } 29 } 30 31 32 33 ClassLoader loader = new FileSystemClassLoader("F:\\classpath"); 34 Class clazz = loader.loadClass("cn.itcast.utils.CommonUtils"); 35 Method method = clazz.getMethod("md5", String.class); 36 String result = (String) method.invoke(null, "qdmmy6"); 37 System.out.println(result);?
?
?
4tomcat類加器 Tomcat 5
Bootstrap>Extension>Application>Common >shared>webappX>jsperLoader
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?> Catalina
/common:tomcat和所有webapp共同使用
/server:tomcat使用 webapp不可見
/shared:所以webapp共同使用 tomcat不能用
?Common:該類加載器包含一些對Tomcat內(nèi)部類和web應(yīng)用可見的額外類。
? ? ? ? ? ? ? ? 其中包括(1)jasper-compiler.jar:JSP 2.0編譯器(2)jsp-api.jar:JSP 2.0 API(3)servlet-api.jar:servlet 2.4 API等等。對應(yīng)文件夾? /common
?Catalina:該加載器初始化用來包含實現(xiàn)Tomcat 5本身所需要所有類和資源;對應(yīng)文件夾 /server
?Shared:在所有的web應(yīng)用程序間共享的類和資源;對應(yīng)文件夾? /shared
?WebappX:為每個部署在單個Tomcat 5實例上的Web應(yīng)用創(chuàng)建的類加載器。
加載/WEB-INF/classes和WEB-INF/lib下的類和資源。
值得注意的是,Web應(yīng)用程序類加載器行為與默認(rèn)的Java 2委派模型不同。當(dāng)一個加載類的請求被WebappX類加載器處理時,類加載器將首先查看本地庫,而非在查看前就委派,
但是也有例外,作為JRE基本類一部分的類不能被覆蓋,但是對與一些類,可以使用J2SE 1.4的Endorsed Standards Override機制。最后,任何包含servlet API的JAR包都將被該類加載器忽略。
?
5Tomcat 6.0:
Bootstrap>Extension>Application>Common > webappX
在tomcat中類的加載稍有不同,如下圖:
?
當(dāng)tomcat啟動時,會創(chuàng)建幾種類加載器:
1 Bootstrap 引導(dǎo)類加載器
加載JVM啟動所需的類,以及標(biāo)準(zhǔn)擴展類(位于jre/lib/ext下)
2 System 系統(tǒng)類加載器
加載tomcat啟動的類,比如bootstrap.jar,通常在catalina.bat或者catalina.sh中指定。
位于CATALINA_HOME/bin下。
3 Common 通用類加載器
加載tomcat使用以及應(yīng)用通用的一些類,位于CATALINA_HOME/lib下,比如servlet-api.jar
4 webapp 應(yīng)用類加載器
每個應(yīng)用在部署后,都會創(chuàng)建一個唯一的類加載器。
該類加載器會加載位于 WEB-INF/lib下的jar文件中的class 和 WEB-INF/classes下的class文件。
當(dāng)應(yīng)用需要到某個類時,則會按照下面的順序進行類加載:
1 使用bootstrap引導(dǎo)類加載器加載
2 使用system系統(tǒng)類加載器加載
3 使用應(yīng)用類加載器在WEB-INF/classes中加載
4 使用應(yīng)用類加載器在WEB-INF/lib中加載
5 使用common類加載器在CATALINA_HOME/lib中加載
問題擴展
通過對上面tomcat類加載機制的理解,就不難明白 為什么java文件放在Eclipse中的src文件夾下會優(yōu)先jar包中的class?
這是因為Eclipse中的src文件夾中的文件java以及webContent中的JSP都會在tomcat啟動時,
被編譯成class文件放在 WEB-INF/class 中。
而Eclipse外部引用的jar包,則相當(dāng)于放在 WEB-INF/lib 中。
因此肯定是 java文件或者JSP文件編譯出的class優(yōu)先加載。
通過這樣,我們就可以簡單的把java文件放置在src文件夾中,通過對該java文件的修改以及調(diào)試,
便于學(xué)習(xí)擁有源碼java文件、卻沒有打包成xxx-source的jar包。
另外呢,開發(fā)者也會因為粗心而犯下面的錯誤。
在 CATALINA_HOME/lib 以及 WEB-INF/lib 中放置了 不同版本的jar包,此時就會導(dǎo)致某些情況下報加載不到類的錯誤。
還有如果多個應(yīng)用使用同一jar包文件,當(dāng)放置了多份,就可能導(dǎo)致 多個應(yīng)用間 出現(xiàn)類加載不到的錯誤。
轉(zhuǎn)載于:https://www.cnblogs.com/wihainan/p/4757245.html
總結(jié)
以上是生活随笔為你收集整理的java class load 类加载的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Exchange 2007服务器启动后,
- 下一篇: SI9000常用共面阻抗模型的解释