java class load 类加载
1:what is ?it
jvm把描述類的數據從class字節碼文件加載到內存,并對數據進行校驗、解析、初始化,最終成為jvm直接使用的數據類型
?1、ClassNotFoundExcetpion?
我們在開發中,經常可以遇見java.lang.ClassNotFoundExcetpion這個異常,今天我就來總結一下這個問題。對于這個異常,它實質涉及到了java技術體系中的類加載。Java的類加載機制是技術體系中比較核心的部分,雖然它和我們直接打交道不多,但是對其背后的機理有一定理解有助于我們排查程序中出現的類加載失敗等技術問題。?
2、類的加載過程?
一個java文件從被加載到被卸載這個生命過程,總共要經歷5個階段,JVM將類加載過程分為:?
加載->鏈接(驗證+準備+解析)->初始化(使用前的準備)->使用->卸載?
(1)加載?
首先通過一個類的全限定名來獲取此類的二進制字節流;其次將這個字節流所代表的靜態存儲結構轉化為方法區的運行時數據結構;最后在java堆中生成一個代表這個類的Class對象,作為方法區這些數據的訪問入口。總的來說就是查找并加載類的二進制數據。?
(2)鏈接:?
驗證:確保被加載類的正確性;?
準備:為類的靜態變量分配內存,并將其初始化為默認值;?
解析:把類中的符號引用轉換為直接引用;?
(3)為類的靜態變量賦予正確的初始值?
3、類的初始化?
(1)類什么時候才被初始化?
1)創建類的實例,也就是new一個對象?
2)訪問某個類或接口的靜態變量,或者對該靜態變量賦值?
3)調用類的靜態方法?
4)反射(Class.forName(“com.lyj.load”))?
5)初始化一個類的子類(會首先初始化子類的父類)?
6)JVM啟動時標明的啟動類,即文件名和類名相同的那個類?
(2)類的初始化順序?
1)如果這個類還沒有被加載和鏈接,那先進行加載和鏈接?
2)假如這個類存在直接父類,并且這個類還沒有被初始化(注意:在一個類加載器中,類只能初始化一次),那就初始化直接的父類(不適用于接口)?
3)加入類中存在初始化語句(如static變量和static塊),那就依次執行這些初始化語句。?
4)總的來說,初始化順序依次是:(靜態變量、靜態初始化塊)–>(變量、初始化塊)–> 構造器;如果有父類,則順序是:父類static方法 –> 子類static方法 –> 父類構造方法- -> 子類構造方法?
2:when it happens
初始化時機??Jvm規范規定了?有且僅有5種
1new getstatic putstatic invokestatic 4條指令時 (new一個對象 使用靜態屬性和方法)
2使用java.lang.reflect包的方法對某個類進行反射調用 forname
3 初始化一個類時發現其父類尚未初始化 先初始化父類
4Jvm啟動時? 加載main方法所在的類
5Jdk1.7的動態語言 MathodHander解析結果為 REF_getStatic REF_putStatic REF_inpokStatic 加載對應類
?
3:ClassLoader
類加載器 實現類加載的動作的類
3.1jvm中的類加載器:
1.引導類(Bootstrap classloader):組成Java平臺的類,包括jre/lib/rt.jar -Xbootclasspath指定? 按文件名識別
2.擴展類(Extensions classloader):使用Java擴展機制的類,jre/lib/ext? java.ext.dirs指定的其他jar
3.用戶類(application classloader 系統、應用類加載器):
由開發者定義的類和沒有利用擴展機制的第三方類,這些類的位置由用戶指定,
一般通過使用-classpath命令行選項或者使用CLASSPATH環境變量來指定其位置。
?3.2層次結構:
Bootstrap > Extension > Application > user classloder
但他們之間不是以繼承的關系來實現 而是組合的形式來復用父加載器的方法
每個累加器都有parent屬性指向它的上級類加器(見下面代碼)
?
3.3雙親委派模型(見下圖)
????? 若一個類加載器收到類加載的請求 他首先自己不會嘗試加載這個類 而是把這個請求委托給父類加器去完成
????? 每一個類加器都如此 這樣所有的類加載請求都會傳入到bootstrap中 只有當父加載器加載不了時
? ? ?此時父加載器拋出異常,子加載器捕獲后 再在自己的領域內嘗試加載
?
比如:
* 代碼中出現了這么一行:new A();
> 系統發現了自己加載的類,其中包含了new A(),這說明需要系統去加載A類
> 系統會給自己的領導打電話:讓擴展去自己的地盤去加載A類
> 擴展會給自己的領導打電話:讓引導去自己的地盤去加載A類
> 引導自己真的去rt.jar中尋找A類
? ?* 如果找到了,那么加載之,然后返回A對應的Class對象給擴展,擴展也會它這個Class返回給系統,結束了!
? ?* 如果沒找到:
? ? ? ?> 引導給擴展返回了一個null,擴展會自己去自己的地盤,去尋找A類
? ? ? ? ? * 如果找到了,那么加載之,然后返回A對應的Class對象給系統,結束了!
? ? ? ? ? * 如果沒找到
? ? ? ? ? ? ? > 擴展返回一個null給系統了,系統去自己的地盤(應用程序下)加載A類
? ? ? ? ? ? ? ? ? * 如果找到了,那么加載之,然后返回這個Class,結束了!
? ? ? ? ? ? ? ? ? * 如果沒找到,拋出異常ClassNotFoundException
?
好處:java類隨著他的類加載器一起具備了一種帶有優先級的層次關系 如java.lang,Object類放在rt.jar內
無論哪一個類加載器要加載它,最終都要委托到啟動類加載器加載他 因此 在各種類加載器環境中使用Object都是同一個類
如果沒有這種委派機制 用 戶自定義一個java.lang.Object放在classpath下
加載時系統將會出現多個不同的object類 Java類型體系的最基礎的行為就會被破壞掉 程序也將一片混亂
?
同一個類: 只有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會執行這一句 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 }先判斷是否已經加載 內存中已有 不再加載
不存在 判斷parent加載器是否為null ? no 則調用父加載器的loadClass方法 否則
該類加載器為bootstrap 他執行自己的加載方法findBootstrapClassOrNull
當父類在自己的領域內找不到時 findClass會拋出異常
子類捕獲異常后 就會嘗試在自己領域內加載
3.6圖解
3.7自定義類加載器
我們也可以通過繼承ClassLoader類來完成自定義類加載器,自類加載器的目的一般是為了加載網絡上的類,因為這會讓class在網絡中傳輸,為了安全,那么class一定是需要加密的,所以需要自定義的類加載器來加載(自定義的類加載器需要做解密工作)。
ClassLoader加載類都是通過loadClass()方法來完成的,loadClass()方法的工作流程如下:
l? 調用findLoadedClass?()方法查看該類是否已經被加載過了,如果該沒有加載過,那么這個方法返回null;
l? 判斷findLoadedClass()方法返回的是否為null,如果不是null那么直接返回,這可以避免同一個類被加載兩次;
l? 如果findLoadedClass()返回的是null,那么就啟動代理模式(委托機制),即調用上級的loadClass()方法,獲取上級的方法是getParent(),當然上級可能還有上級,這個動作就一直向上走;
l? 如果getParent().loadClass()返回的不是null,這說明上級加載成功了,那么就加載結果;
l? 如果上級返回的是null,這說明需要自己出手了,這時loadClass()方法會調用本類的findClass()方法來加載類;
l? 這說明我們只需要重寫ClassLoader的findClass()方法,這就可以了!如果重寫了loadClass()方法覆蓋了代理模式!
?
OK,通過上面的分析,我們知道要自定義一個類加載器,只需要繼承ClassLoader類,然后重寫它的findClass()方法即可。那么在findClass()方法中我們要完成哪些工作呢?
l? 找到class文件,把它加載到一個byte[]中;
l? 調用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內部類和web應用可見的額外類。
? ? ? ? ? ? ? ? 其中包括(1)jasper-compiler.jar:JSP 2.0編譯器(2)jsp-api.jar:JSP 2.0 API(3)servlet-api.jar:servlet 2.4 API等等。對應文件夾? /common
?Catalina:該加載器初始化用來包含實現Tomcat 5本身所需要所有類和資源;對應文件夾 /server
?Shared:在所有的web應用程序間共享的類和資源;對應文件夾? /shared
?WebappX:為每個部署在單個Tomcat 5實例上的Web應用創建的類加載器。
加載/WEB-INF/classes和WEB-INF/lib下的類和資源。
值得注意的是,Web應用程序類加載器行為與默認的Java 2委派模型不同。當一個加載類的請求被WebappX類加載器處理時,類加載器將首先查看本地庫,而非在查看前就委派,
但是也有例外,作為JRE基本類一部分的類不能被覆蓋,但是對與一些類,可以使用J2SE 1.4的Endorsed Standards Override機制。最后,任何包含servlet API的JAR包都將被該類加載器忽略。
?
5Tomcat 6.0:
Bootstrap>Extension>Application>Common > webappX
在tomcat中類的加載稍有不同,如下圖:
?
當tomcat啟動時,會創建幾種類加載器:
1 Bootstrap 引導類加載器
加載JVM啟動所需的類,以及標準擴展類(位于jre/lib/ext下)
2 System 系統類加載器
加載tomcat啟動的類,比如bootstrap.jar,通常在catalina.bat或者catalina.sh中指定。
位于CATALINA_HOME/bin下。
3 Common 通用類加載器
加載tomcat使用以及應用通用的一些類,位于CATALINA_HOME/lib下,比如servlet-api.jar
4 webapp 應用類加載器
每個應用在部署后,都會創建一個唯一的類加載器。
該類加載器會加載位于 WEB-INF/lib下的jar文件中的class 和 WEB-INF/classes下的class文件。
當應用需要到某個類時,則會按照下面的順序進行類加載:
1 使用bootstrap引導類加載器加載
2 使用system系統類加載器加載
3 使用應用類加載器在WEB-INF/classes中加載
4 使用應用類加載器在WEB-INF/lib中加載
5 使用common類加載器在CATALINA_HOME/lib中加載
問題擴展
通過對上面tomcat類加載機制的理解,就不難明白 為什么java文件放在Eclipse中的src文件夾下會優先jar包中的class?
這是因為Eclipse中的src文件夾中的文件java以及webContent中的JSP都會在tomcat啟動時,
被編譯成class文件放在 WEB-INF/class 中。
而Eclipse外部引用的jar包,則相當于放在 WEB-INF/lib 中。
因此肯定是 java文件或者JSP文件編譯出的class優先加載。
通過這樣,我們就可以簡單的把java文件放置在src文件夾中,通過對該java文件的修改以及調試,
便于學習擁有源碼java文件、卻沒有打包成xxx-source的jar包。
另外呢,開發者也會因為粗心而犯下面的錯誤。
在 CATALINA_HOME/lib 以及 WEB-INF/lib 中放置了 不同版本的jar包,此時就會導致某些情況下報加載不到類的錯誤。
還有如果多個應用使用同一jar包文件,當放置了多份,就可能導致 多個應用間 出現類加載不到的錯誤。
轉載于:https://www.cnblogs.com/wihainan/p/4757245.html
總結
以上是生活随笔為你收集整理的java class load 类加载的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Exchange 2007服务器启动后,
- 下一篇: SI9000常用共面阻抗模型的解释