摘要:作用負責將加載到中審查每個類由誰加載父優先的等級加載機制將字節碼重新解析成統一要求的對象格式類結構分析為了更好的理解類的加載機制,我們來深入研究一下和他的方法。就算兩個是同一份字節碼,如果被兩個不同的實例所加載,也會認為它們是兩個不同。
申明:本文首發于 詳細深入分析 ClassLoader 工作機制 ,如有轉載,注明原出處即可,謝謝配合。
什么是 ClassLoader ?大家都知道,當我們寫好一個 Java 程序之后,不是管是 C/S 還是 B/S 應用,都是由若干個 .class 文件組織而成的一個完整的 Java 應用程序,當程序在運行時,即會調用該程序的一個入口函數來調用系統的相關功能,而這些功能都被封裝在不同的 class 文件當中,所以經常要從這個 class 文件中要調用另外一個 class 文件中的方法,如果另外一個文件不存在的,則會引發系統異常。而程序在啟動的時候,并不會一次性加載程序所要用的所有class文件,而是根據程序的需要,通過Java的類加載機制(ClassLoader)來動態加載某個 class 文件到內存當中的,從而只有 class 文件被載入到了內存之后,才能被其它 class 所引用。所以 ClassLoader 就是用來動態加載 class 文件到內存當中用的。
ClassLoader 作用:負責將 Class 加載到 JVM 中
審查每個類由誰加載(父優先的等級加載機制)
將 Class 字節碼重新解析成 JVM 統一要求的對象格式
1、ClassLoader 類結構分析為了更好的理解類的加載機制,我們來深入研究一下 ClassLoader 和他的方法。
public abstract class ClassLoader
ClassLoader類是一個抽象類,sun公司是這么解釋這個類的:
/** * A class loader is an object that is responsible for loading classes. The * class ClassLoader is an abstract class. Given the binary name of a class, a class loader should attempt to * locate or generate data that constitutes a definition for the class. A * typical strategy is to transform the name into a file name and then read a * "class file" of that name from a file system. **/
大致意思如下:
class loader 是一個負責加載 classes 的對象,ClassLoader 類是一個抽象類,需要給出類的二進制名稱,class loader 嘗試定位或者產生一個 class 的數據,一個典型的策略是把二進制名字轉換成文件名然后到文件系統中找到該文件。
以下是 ClassLoader 常用到的幾個方法及其重載方法:
ClassLoader
defineClass(byte[], int, int) 把字節數組 b中的內容轉換成 Java 類,返回的結果是java.lang.Class類的實
例。這個方法被聲明為final的
findClass(String name) 查找名稱為 name的類,返回的結果是java.lang.Class類的實例
loadClass(String name) 加載名稱為 name的類,返回的結果是java.lang.Class類的實例
resolveClass(Class>) 鏈接指定的 Java 類
其中 defineClass 方法用來將 byte 字節流解析成 JVM 能夠識別的 Class 對象,有了這個方法意味著我們不僅僅可以通過 class 文件實例化對象,還可以通過其他方式實例化對象,如果我們通過網絡接收到一個類的字節碼,拿到這個字節碼流直接創建類的 Class 對象形式實例化對象。如果直接調用這個方法生成類的 Class 對象,這個類的 Class 對象還沒有 resolve ,這個 resolve 將會在這個對象真正實例化時才進行。
接下來我們看loadClass方法的實現方式:
protected Class> loadClass(String name, boolean resolve) throws ClassNotFoundException { synchronized (getClassLoadingLock(name)) { // First, check if the class has already been loaded Class c = findLoadedClass(name); if (c == null) { long t0 = System.nanoTime(); try { if (parent != null) { c = parent.loadClass(name, false); } else { c = findBootstrapClassOrNull(name); } } catch (ClassNotFoundException e) { // ClassNotFoundException thrown if class not found // from the non-null parent class loader } if (c == null) { // If still not found, then invoke findClass in order // to find the class. long t1 = System.nanoTime(); c = findClass(name); // this is the defining class loader; record the stats sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0); sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1); sun.misc.PerfCounter.getFindClasses().increment(); } } if (resolve) { resolveClass(c); } return c; } }
該方法大概意思:
2、ClassLoader 的等級加載機制 Java默認提供的三個ClassLoader使用指定的二進制名稱來加載類,這個方法的默認實現按照以下順序查找類: 調用findLoadedClass(String) 方法檢查這個類是否被加載過 使用父加載器調用 loadClass(String) 方法,如果父加載器為 Null,類加載器裝載虛擬機內置的加載器調用 findClass(String) 方法裝載類, 如果,按照以上的步驟成功的找到對應的類,并且該方法接收的 resolve 參數的值為 true,那么就調用resolveClass(Class) 方法來處理類。 ClassLoader 的子類最好覆蓋 findClass(String) 而不是這個方法。 除非被重寫,這個方法默認在整個裝載過程中都是同步的(線程安全的)。
BootStrap ClassLoader:稱為啟動類加載器,是Java類加載層次中最頂層的類加載器,負責加載JDK中的核心類庫,如:rt.jar、resources.jar、charsets.jar等,可通過如下程序獲得該類加載器從哪些地方加載了相關的jar或class文件:
public class BootStrapTest { public static void main(String[] args) { URL[] urls = sun.misc.Launcher.getBootstrapClassPath().getURLs(); for (int i = 0; i < urls.length; i++) { System.out.println(urls[i].toExternalForm()); } } }
以下內容是上述程序從本機JDK環境所獲得的結果:
其實上述結果也是通過查找 sun.boot.class.path 這個系統屬性所得知的。
System.out.println(System.getProperty("sun.boot.class.path"));
打印結果:C:Javajdk1.8.0_60jrelib esources.jar;C:Javajdk1.8.0_60jrelib t.jar;C:Javajdk1.8.0_60jrelibsunrsasign.jar;C:Javajdk1.8.0_60jrelibjsse.jar;C:Javajdk1.8.0_60jrelibjce.jar;C:Javajdk1.8.0_60jrelibcharsets.jar;C:Javajdk1.8.0_60jrelibjfr.jar;C:Javajdk1.8.0_60jreclasses
Extension ClassLoader:稱為擴展類加載器,負責加載Java的擴展類庫,Java 虛擬機的實現會提供一個擴展庫目錄。該類加載器在此目錄里面查找并加載 Java 類。默認加載JAVA_HOME/jre/lib/ext/目下的所有jar。
App ClassLoader:稱為系統類加載器,負責加載應用程序classpath目錄下的所有jar和class文件。一般來說,Java 應用的類都是由它來完成加載的。可以通過 ClassLoader.getSystemClassLoader()來獲取它。
?
除了系統提供的類加載器以外,開發人員可以通過繼承java.lang.ClassLoader類的方式實現自己的類加載器,以滿足一些特殊的需求。
除了引導類加載器之外,所有的類加載器都有一個父類加載器。 給出的 getParent()方法可以得到。對于
系統提供的類加載器來說,系統類加載器的父類加載器是擴展類加載器,而擴展類加載器的父類加載器是引導類加載器;對于開發人員編寫的類加載器來說,其父類加載器是加載此類加載器 Java 類的類加載器。因為類加載器 Java 類如同其它的 Java 類一樣,也是要由類加載器來加載的。一般來說,開發人員編寫的類加載器的父類加載器是系統類加載器。類加載器通過這種方式組織起來,形成樹狀結構。樹的根節點就是引導類加載器。
?
ClassLoader加載類的原理ClassLoader使用的是雙親委托模型來搜索類的,每個ClassLoader實例都有一個父類加載器的引用(不是繼承的關系,是一個包含的關系),虛擬機內置的類加載器(Bootstrap ClassLoader)本身沒有父類加載器,但可以用作其它ClassLoader實例的的父類加載器。當一個ClassLoader實例需要加載某個類時,它會試圖親自搜索某個類之前,先把這個任務委托給它的父類加載器,這個過程是由上至下依次檢查的,首先由最頂層的類加載器Bootstrap ClassLoader試圖加載,如果沒加載到,則把任務轉交給Extension ClassLoader試圖加載,如果也沒加載到,則轉交給App ClassLoader 進行加載,如果它也沒有加載得到的話,則返回給委托的發起者,由它到指定的文件系統或網絡等URL中加載該類。如果它們都沒有加載到這個類時,則拋出ClassNotFoundException異常。否則將這個找到的類生成一個類的定義,并將它加載到內存當中,最后返回這個類在內存中的Class實例對象。
因為這樣可以避免重復加載,當父親已經加載了該類的時候,就沒有必要 ClassLoader再加載一次。考慮到安全因素,我們試想一下,如果不使用這種委托模式,那我們就可以隨時使用自定義的String來動態替代java核心api中定義的類型,這樣會存在非常大的安全隱患,而雙親委托的方式,就可以避免這種情況,因為String已經在啟動時就被引導類加載器(Bootstrcp ClassLoader)加載,所以用戶自定義的ClassLoader永遠也無法加載一個自己寫的String,除非你改變JDK中ClassLoader搜索類的默認算法。
JVM在判定兩個class是否相同時,不僅要判斷兩個類名是否相同,而且要判斷是否由同一個類加載器實例加載的。只有兩者同時滿足的情況下,JVM才認為這兩個class是相同的。就算兩個class是同一份class字節碼,如果被兩個不同的ClassLoader實例所加載,JVM也會認為它們是兩個不同class。比如網絡上的一個Java類org.classloader.simple.NetClassLoaderSimple,javac編譯之后生成字節碼文件NetClassLoaderSimple.class,ClassLoaderA和ClassLoaderB這兩個類加載器并讀取了NetClassLoaderSimple.class文件,并分別定義出了java.lang.Class實例來表示這個類,對于JVM來說,它們是兩個不同的實例對象,但它們確實是同一份字節碼文件,如果試圖將這個Class實例生成具體的對象進行轉換時,就會拋運行時異常java.lang.ClassCaseException,提示這是兩個不同的類型。現在通過實例來驗證上述所描述的是否正確:
1)、在web服務器上建一個org.classloader.simple.NetClassLoaderSimple.java類
public class NetClassLoaderSimple { private NetClassLoaderSimple instance; public void setNetClassLoaderSimple(Object object){ this.instance = (NetClassLoaderSimple)object; } }
org.classloader.simple.NetClassLoaderSimple類的setNetClassLoaderSimple方法接收一個Object類型參數,并將它強制轉換成org.classloader.simple.NetClassLoaderSimple類型。
2)、測試兩個class是否相同 NetWorkClassLoader.java
package classloader; public class NewworkClassLoaderTest { public static void main(String[] args) { try { //測試加載網絡中的class文件 String rootUrl = "http://localhost:8080/httpweb/classes"; String className = "org.classloader.simple.NetClassLoaderSimple"; NetworkClassLoader ncl1 = new NetworkClassLoader(rootUrl); NetworkClassLoader ncl2 = new NetworkClassLoader(rootUrl); Class> clazz1 = ncl1.loadClass(className); Class> clazz2 = ncl2.loadClass(className); Object obj1 = clazz1.newInstance(); Object obj2 = clazz2.newInstance(); clazz1.getMethod("setNetClassLoaderSimple", Object.class).invoke(obj1, obj2); } catch (Exception e) { e.printStackTrace(); } } }
首先獲得網絡上一個class文件的二進制名稱,然后通過自定義的類加載器NetworkClassLoader創建兩個實例,并根據網絡地址分別加載這份class,并得到這兩個ClassLoader實例加載后生成的Class實例clazz1和clazz2,最后將這兩個Class實例分別生成具體的實例對象obj1和obj2,再通過反射調用clazz1中的setNetClassLoaderSimple方法。
3)、查看測試結果
結論:從結果中可以看出,運行時拋出了java.lang.ClassCastException異常。雖然兩個對象obj1和 obj2的類的名字相同,但是這兩個類是由不同的類加載器實例來加載的,所以JVM認為它們就是兩個不同的類。
了解了這一點之后,就可以理解代理模式的設計動機了。代理模式是為了保證 Java 核心庫的類型安全。所有 Java 應用都至少需要引用 java.lang.Object類,也就是說在運行的時候,java.lang.Object這個類需要被加載到 Java 虛擬機中。如果這個加載過程由 Java 應用自己的類加載器來完成的話,很可能就存在多個版本的 java.lang.Object類,而且這些類之間是不兼容的。通過代理模式,對于 Java 核心庫的類的加載工作由引導類加載器來統一完成,保證了Java 應用所使用的都是同一個版本的 Java 核心庫的類,是互相兼容的。
不同的類加載器為相同名稱的類創建了額外的名稱空間。相同名稱的類可以并存在 Java 虛擬機中,只需要用不同的類加載器來加載它們即可。不同類加載器加載的類之間是不兼容的,這就相當于在 Java 虛擬機內部創建了一個個相互隔離的 Java 類空間。
ClassLoader的體系架構: 類加載器的樹狀組織結構測試一:
public class ClassLoaderTree { public static void main(String[] args) { ClassLoader loader = ClassLoaderTree.class.getClassLoader(); while (loader!=null){ System.out.println(loader.toString()); loader = loader.getParent(); } System.out.println(loader); } }
每個 Java 類都維護著一個指向定義它的類加載器的引用,通過 getClassLoader()方法就可以獲取到此引用。代碼中通過遞歸調用 getParent()方法來輸出全部的父類加載器。
結果是:
第一個輸出的是 ClassLoaderTree類的類加載器,即系統類加載器。它是sun.misc.Launcher$AppClassLoader類的實例;第二個輸出的是擴展類加載器,是sun.misc.Launcher$ExtClassLoader類的實例。需要注意的是這里并沒有輸出引導類加載器,這是由于有些 JDK 的實現對于父類加載器是引導類加載器的情況,getParent()方法返回 null。第三行結果說明:ExtClassLoader的類加器是Bootstrap ClassLoader,因為Bootstrap ClassLoader不是一個普通的Java類,所以ExtClassLoader的parent=null,所以第三行的打印結果為null就是這個原因。
測試二:
將ClassLoaderTree.class打包成ClassLoaderTree.jar,放到Extension ClassLoader的加載目錄下(JAVA_HOME/jre/lib/ext),然后重新運行這個程序,得到的結果會是什么樣呢?
此處我在 IDEA 中的運行結果還和上面的一樣,與文章 深入分析Java ClassLoader原理 中的有差距,具體原因未弄清楚,還希望讀者能夠親自測試。
那文章中的結果是:
打印結果分析:
為什么第一行的結果是ExtClassLoader呢?
因為 ClassLoader 的委托模型機制,當我們要用 ClassLoaderTest.class 這個類的時候,AppClassLoader 在試圖加載之前,先委托給 Bootstrcp ClassLoader,Bootstracp ClassLoader 發現自己沒找到,它就告訴 ExtClassLoader,兄弟,我這里沒有這個類,你去加載看看,然后 Extension ClassLoader 拿著這個類去它指定的類路徑(JAVA_HOME/jre/lib/ext)試圖加載,唉,它發現在ClassLoaderTest.jar 這樣一個文件中包含 ClassLoaderTest.class 這樣的一個文件,然后它把找到的這個類加載到內存當中,并生成這個類的 Class 實例對象,最后把這個實例返回。所以 ClassLoaderTest.class 的類加載器是 ExtClassLoader。
第二行的結果為null,是因為ExtClassLoader的父類加載器是Bootstrap ClassLoader。
JVM加載class文件的兩種方法;隱式加載, 程序在運行過程中當碰到通過new 等方式生成對象時,隱式調用類裝載器加載對應的類到jvm中。
顯式加載, 通過class.forname()、this.getClass.getClassLoader().loadClass()等方法顯式加載需要的類,或者我們自己實現的 ClassLoader 的 findlass() 方法。
下面介紹下 class.forName的加載類方法:
類加載的動態性體現:Class.forName是一個靜態方法,同樣可以用來加載類。該方法有兩種形式:Class.forName(String name,boolean initialize, ClassLoader loader)和Class.forName(String className)。第一種形式的參數 name表示的是類的全名;initialize表示是否初始化類;loader表示加載時使用的類加載器。第二種形式則相當于設置了參數 initialize的值為 true,loader的值為當前類的類加載器。Class.forName的一個很常見的用法是在加載數據庫驅動的時候。如
Class.forName("org.apache.derby.jdbc.EmbeddedDriver")用來加載 Apache Derby 數據庫的驅動。
一個應用程序總是由n多個類組成,Java程序啟動時,并不是一次把所有的類全部加載后再運行,它總是先把保證程序運行的基礎類一次性加載到jvm中,其它類等到jvm用到的時候再加載,這樣的好處是節省了內存的開銷,因為java最早就是為嵌入式系統而設計的,內存寶貴,這是一種可以理解的機制,而用到時再加載這也是java動態性的一種體現。
3、如何加載 class 文件第一階段找到 .class 文件并把這個文件包含的字節碼加載到內存中。
第二階段中分三步,字節碼驗證;class 類數據結構分析及相應的內存分配;最后的符號表的鏈接。
第三階段是類中靜態屬性和初始化賦值,以及靜態塊的執行等。
3.1 、加載字節碼到內存。。
3.2 、驗證與分析字節碼驗證,類裝入器對于類的字節碼要做很多檢測,以確保格式正確,行為正確。
類裝備,準備代表每個類中定義的字段、方法和實現接口所必須的數據結構。
解析,裝入器裝入類所引用的其他所有類。
4、常見加載類錯誤分析 4.1 、 ClassNotFoundExecptionClassNotFoundExecption 異常是平常碰到的最多的。這個異常通常發生在顯示加載類的時候。
public class ClassNotFoundExceptionTest { public static void main(String[] args) { try { Class.forName("NotFoundClass"); }catch (ClassNotFoundException e){ e.printStackTrace(); } } }
顯示加載一個類通常有:
通過類 Class 中的 forName() 方法
通過類 ClassLoader 中的 loadClass() 方法
通過類 ClassLoader 中的 findSystemClass() 方法
出現這種錯誤其實就是當 JVM 要加載指定文件的字節碼到內存時,并沒有找到這個文件對應的字節碼,也就是這個文件并不存在。解決方法就是檢查在當前的 classpath 目錄下有沒有指定的文件。
4.2 、 NoClassDefFoundError在JavaDoc中對NoClassDefFoundError的產生可能的情況就是使用new關鍵字、屬性引用某個類、繼承了某個接口或者類,以及方法的某個參數中引用了某個類,這時就會觸發JVM或者類加載器實例嘗試加載類型的定義,但是該定義卻沒有找到,影響了執行路徑。換句話說,在編譯時這個類是能夠被找到的,但是在執行時卻沒有找到。
解決這個錯誤的方法就是確保每個類引用的類都在當前的classpath下面。
4.3 、 UnsatisfiedLinkError該錯誤通常是在 JVM 啟動的時候,如果 JVM 中的某個 lib 刪除了,就有可能報這個錯誤。
public class UnsatisfiedLinkErrorTest { public native void nativeMethod(); static { System.loadLibrary("NoLib"); } public static void main(String[] args) { new UnsatisfiedLinkErrorTest().nativeMethod(); //解析native標識的方法時JVM找不到對應的庫文件 } }4.4 、 ClassCastException
該錯誤通常出現強制類型轉換時出現這個錯誤。
public class ClassCastExceptionTest { public static Map m = new HashMap(){ { put("a", "2"); } }; public static void main(String[] args) { Integer integer = (Integer) m.get("a"); //將m強制轉換成Integer類型 System.out.println(integer); } }
注意:JVM 在做類型轉換時的規則:
對于普通對象,對象必須是目標類的實例或目標類的子類的實例。如果目標類是接口,那么會把它當作實現了該接口的一個子類。
對于數組類型,目標類必須是數組類型或 java.lang.Object、java.lang.Cloneable、java.io.Serializable。
如果不滿足上面的規則,JVM 就會報錯,有兩種方式可避免錯誤:
在容器類型中顯式的指明這個容器所包含的對象類型。
先通過 instanceof 檢查是不是目標類型,然后再進行強制類型的轉換。
上面代碼中改成如下就可以避免錯誤了:
4.5 、 ExceptionInInitializerErrorpublic class ExceptionInInitializerErrorTest { public static Map m = new HashMap(){{ m.put("a", "2"); }}; public static void main(String[] args) { Integer integer = (Integer) m.get("a"); System.out.println(integer); } }
在初始化這個類時,給靜態屬性 m 賦值時出現了異常導致拋出錯誤 ExceptionInInitializerError。
4.6 NoSuchMethodErrorNoSuchMethodError代表這個類型確實存在,但是一個不正確的版本被加載了。為了解決這個問題我們可以使用 ‘-verbose:class’ 來判斷該JVM加載的到底是哪個版本。
4.7 LinkageError有時候事情會變得更糟,和 ClassCastException 本質一樣,加載自不同位置的相同類在同一段邏輯(比如:方法)中交互時,會出現 LinkageError 。
LinkageError 需要觀察哪個類被不同的類加載器加載了,在哪個方法或者調用處發生(交匯)的,然后才能想解決方法,解決方法無外乎兩種。第一,還是不同的類加載器加載,但是相互不再交匯影響,這里需要針對發生問題的地方做一些改動,比如更換實現方式,避免出現上述問題;第二,沖突的類需要由一個Parent類加載器進行加載。LinkageError 和ClassCastException 本質是一樣的,加載自不同類加載器的類型,在同一個類的方法或者調用中出現,如果有轉型操作那么就會拋 ClassCastException ,如果是直接的方法調用處的參數或者返回值解析,那么就會產生 LinkageError 。
5、常用的 ClassLoader 分析。。參見書籍《深入分析Java Web技術內幕》
6、如何實現自己的 ClassLoaderClassLoader 能夠完成的事情有以下情況:
在自定義路徑下查找自定義的class類文件。
對我們自己要加載的類做特殊處理。
可以定義類的實現機制。
雖然在絕大多數情況下,系統默認提供的類加載器實現已經可以滿足需求。但是在某些情況下,您還是需要為應用開發出自己的類加載器。比如您的應用通過網絡來傳輸 Java 類的字節代碼,為了保證安全性,這些字節代碼經過了加密處理。這個時候您就需要自己的類加載器來從某個網絡地址上讀取加密后的字節代碼,接著進行解密和驗證,最后定義出要在 Java 虛擬機中運行的類來。
定義自已的類加載器分為兩步:
1、繼承java.lang.ClassLoader
2、重寫父類的findClass方法
加載存儲在文件系統上的 Java 字節代碼。
public class FileSystemClassLoader extends ClassLoader { private String rootDir; public FileSystemClassLoader(String rootDir){ this.rootDir = rootDir; } protected Class> findClass(String name) throws ClassNotFoundException { byte[] classData = getClassData(name); if (classData == null){ throw new ClassNotFoundException(); } else { return defineClass(name, classData, 0, classData.length); } } private byte[] getClassData(String className) { String path = classNameToPath(className); try { InputStream ins = new FileInputStream(path); ByteArrayOutputStream baos = new ByteArrayOutputStream(); int bufferSize = 4096; byte[] buffer = new byte[bufferSize]; int bytesNumRead = 0; while ((bytesNumRead = ins.read(buffer)) != -1){ baos.write(buffer, 0, bytesNumRead); } return baos.toByteArray(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } return null; } private String classNameToPath(String className) { return rootDir + File.separatorChar + className.replace(".", File.separatorChar) + ".class"; } }
類 FileSystemClassLoader繼承自類java.lang.ClassLoader。java.lang.ClassLoader類的方法loadClass()封裝了前面提到的代理模式的實現。該方法會首先調用 findLoadedClass()方法來檢查該類是否已經被加載過;如果沒有加載過的話,會調用父類加載器的loadClass()方法來嘗試加載該類;如果父類加載器無法加載該類的話,就調用 findClass()方法來查找該類。因此,為了保證類加載器都正確實現代理模式,在開發自己的類加載器時,最好不要覆寫 loadClass()方法,而是覆寫findClass()方法。
類 FileSystemClassLoader的 findClass()方法首先根據類的全名在硬盤上查找類的字節代碼文件(.class 文
件),然后讀取該文件內容,最后通過 defineClass()方法來把這些字節代碼轉換成 java.lang.Class類的實例。
一個網絡類加載器來說明如何通過類加載器來實現組件的動態更新。即基本的場景是:Java 字節代碼(.class)文件存放在服務器上,客戶端通過網絡的方式獲取字節代碼并執行。當有版本更新的時候,只需要替換掉服務
器上保存的文件即可。通過類加載器可以比較簡單的實現這種需求。
類 NetworkClassLoader 負責通過網絡下載 Java 類字節代碼并定義出 Java 類。它的實現與FileSystemClassLoader 類似。在通過 NetworkClassLoader 加載了某個版本的類之后,一般有兩種做法來使用它。第一種做法是使用 Java 反射 API。另外一種做法是使用接口。需要注意的是,并不能直接在客戶端代碼中引用從服務器上下載的類,因為客戶端代碼的類加載器找不到這些類。使用 Java 反射 API 可以直接調用 Java 類的
方法。而使用接口的做法則是把接口的類放在客戶端中,從服務器上加載實現此接口的不同版本的類。在客戶端通過相同的接口來使用這些實現類。
網絡類加載器的代碼:ClassLoader
7、類加載器與Web容器對于運行在 Java EE?容器中的 Web 應用來說,類加載器的實現方式與一般的 Java 應用有所不同。不同的 Web 容器的實現方式也會有所不同。以 Apache Tomcat 來說,每個Web 應用都有一個對應的類加載器實例。該類加載器也使用代理模式,所不同的是它是首先嘗試去加載某個類,如果找不到再代理給父類加載器。這與一般類加載器的順序是相反的。這是 Java Servlet 規范中的推薦做法,其目的是使得Web 應用自己的類的優先級高于 Web 容器提供的類。這種代理模式的一個例外是:Java 核心庫的類是不在查找范圍之內的。這也是為了保證 Java 核心庫的類型安全。
絕大多數情況下,Web 應用的開發人員不需要考慮與類加載器相關的細節。下面給出幾條簡單的原則:
每個 Web 應用自己的 Java 類文件和使用的庫的 jar 包,分別放在 WEB-INF/classes和 WEB-INF/lib目錄下面。
多個應用共享的 Java 類文件和 jar 包,分別放在 Web 容器指定的由所有 Web 應用共享的目錄下面。
當出現找不到類的錯誤時,檢查當前類的類加載器和當前線程的上下文類加載器是否正確
8、總結本篇文章詳細深入的介紹了 ClassLoader 的工作機制,還寫了如何自己實現所需的 ClassLoader 。
參考資料1、深度分析 Java 的 ClassLoader 機制(源碼級別)
2、深入淺出ClassLoader
3、深入探討 Java 類加載器
4、深入分析Java ClassLoader原理
5、《深入分析 Java Web 技術內幕》修訂版 —— 深入分析 ClassLoader 工作機制
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/66723.html
摘要:最終形成可以被虛擬機最直接使用的類型的過程就是虛擬機的類加載機制。即重寫一個類加載器的方法驗證驗證是連接階段的第一步,這一階段的目的是為了確保文件的字節流中包含的信息符合當前虛擬機的要求,并且不會危害虛擬機自身的安全。 《深入理解Java虛擬機:JVM高級特性與最佳實踐(第二版》讀書筆記與常見相關面試題總結 本節常見面試題(推薦帶著問題閱讀,問題答案在文中都有提到): 簡單說說類加載過...
摘要:的打包結構改動是這個引入的這個的本意是簡化的繼承關系,以一種直觀的優先的方式來實現,同時打包結構和傳統的包應用更接近。目前的繼承關系帶來的一些影響有很多用戶可能會發現,一些代碼在里跑得很好,但是在實際部署運行時不工作。 前言 對spring boot本身啟動原理的分析,請參考:http://hengyunabc.github.io/s... Spring boot里的ClassLoad...
摘要:而字節碼運行在之上,所以不用關心字節碼是在哪個操作系統編譯的,只要符合規范,那么,這個字節碼文件就是可運行的。好處防止內存中出現多份同樣的字節碼安全性角度特別說明類加載器在成功加載某個類之后,會把得到的類的實例緩存起來。 前言 只有光頭才能變強 JVM在準備面試的時候就有看了,一直沒時間寫筆記。現在到了一家公司實習,閑的時候就寫寫,刷刷JVM博客,刷刷電子書。 學習JVM的目的也很簡單...
閱讀 1452·2021-09-22 16:04
閱讀 2808·2019-08-30 15:44
閱讀 896·2019-08-30 15:43
閱讀 774·2019-08-29 15:24
閱讀 1855·2019-08-29 14:07
閱讀 1143·2019-08-29 12:30
閱讀 1738·2019-08-29 11:15
閱讀 2750·2019-08-28 18:08