你想寫類加載器?或者你遇到了ClassCastException異常,或者你遇到了奇怪的LinkageError狀態約束異常。應該仔細看看java類的加載處理了。
一個Java類是由java.lang.ClassLoader類的一個實例加載的。由於java.lang.ClassLoader自己本身是一個抽象類所以一個類加載器只能夠是java.lang.ClassLoader類的具體子類的實例。如果是這種情況,那麼哪一個類加載器來加載java.lang.ClassLoader這個類?(經典的"誰將會加載加載者"引導的問題)。事實證明JVM有一個內置的引導類加載器。引導加載器加載java.lang.ClassLoader和許多其他java平台類。
要加載一個具體的java類,例如com.acme.Foo,JVM調用java.lang.ClassLoader類的loadClass方法(事實上,JVM查找loadClassInternal方法-如果發現loadClassInternal方法則用loadClassInternal方法,否則JVM使用loadClass方法,而loadClassInternal方法會調用loadClass方法)。loadClass方法接收類名來加載類返回表示加載的類的java.lang.Class實例。事實上loadClass方法找到.class文件(或者URL)的實際字節,並調用defineClass方法來構造出java.lang.Class類的字節數組。加載器上調用loadClass方法的加載器稱之為初始化加載器(即,JVM啟動加載使用這個加載器).但是,啟動加載器不是直接加載類的-而是可能委托給另外一個類加載器(例如,它的父加載器)-它自己也可能委派給另外一個加載器去加載等等。最終在委托鏈中的某些類加載器對象調用defineClass方法加載有關的類(com.acme.Foo)。
這個特殊的類加載器叫做com.acme.Foo的確切加載器。在運行時,一個java類是由類的完全限定類名和和類加載器確定其唯一性的。如果指定相同的類名(即,相同的完全限定類名)的類是由兩個不同的類加載器加載的,那麼這些類是不同的-即使這些.class的字節碼是相同的並且都是從相同的位置進行加載的(相同的URL)。
即便是一個簡單的"hell world"java程序,也有至少3種類加載器。
引導類加載器
讓我們假設你正在運行一個"hello world" java程序。我們來看一下類的加載流程。JVM用應用類加載器加載主方法(main)所在的類。如果你運行下面的程序
class Main {
public static void main(String[] args) {
System.out.println(Main.class.getClassLoader());
javax.swing.JFrame f = new javax.swing.JFrame();
f.setVisible(true);
SomeAppClass s = new SomeAppClass();
}
它會打印如下內容
sun.misc.Launcher$AppClassLoader@17943a4
每當一些其它的類引用在Main類中被解析時,JVM用Main所在類的明確的加載器-應用類加載器-做為初始化加載器。在上面的列子中,為了加載javax.swing.JFrame類JVM將使用應用類加載器做為一個初始化加載器。即,JVM將用應用類應用做為初始化加載器。即。JVM將調用loadClass()方法(loadClassInternal方法)在應用類加載器中。應用類加載器委托給擴展類加載器。
擴展加載器檢查這是否是一個啟動類(用私有方法 - ClassLoader.findBootstrapClass),啟動類加載器是否從rt.jar加載過它。
當SomeAppClass的引用類被解析時,JVM有著相同的過程-用應用類加載器做為初始化加載器。
應用加載器委托給擴展加載器,擴展加載器檢查啟動加載器,啟動加載器找不到"SomeAppClass"類,
於是擴展加載器檢查"SomeAppClass"類是否在擴展jars裡,結果發現不在。
於是應用類加載器檢查在應用的CLASSPATH下的.class字節,如果找到了則進行加載,如果沒有找到,將會拋出NoClassDefFoundError異常。
Class是由具體的類加載器與類的完全限定類名唯一定義的。
如果具體的類加載器不同,即使.class字符是從文件系統中的相同位置進行加載的Classes也是不同的。
類加載器委托給父加載器進行加載。
加載Bar類中引用的Foo類,JVM使用Bar類的確切的類加載器做為初始化加載器。JVM會在Bar類的確切加載器上會調用loadClass()方法加載Foo類。
JVM緩存->運行時的類每次初始化加載都將被記錄。JVM將會緩存用於以後的解析。即,loadClass()方法不會對於每一次引用都調用。這能確保時間的不變性-即,一個類加載器不允許加載相同類名但字節碼不同的類。
他是由緩存來實現的。好的類加載器應該通過調用ClassLoader得call()方法來檢查緩存。
Understanding Java class loading https://blogs.oracle.com/sundararajan/entry/understanding_java_class_loading