1.java源碼和class文件Class
java源碼都被編譯為class文件,jvm真正啟動之后還是要找到對應(yīng)的Class,靠的就是ClassLoader
2.啟動以及三個ClassLoader
當我們通過命令java XXMain.class的時候,java.exe根據(jù)我們的設(shè)置找到了JRE,接著找到JRE鐘的jvm.dll,真正的java虛擬機,最后載入這個動態(tài)函數(shù)庫,啟動Java虛擬機,虛擬機一啟動,首先做一些初始化的動作,獲得系統(tǒng)參數(shù)等,初始化完成后,就會產(chǎn)生第一個ClassLoader,即Bootstrap Loader,這個Loader除了初始化動作之外,最重要的就是載入定義在sun.misc命名空間下的Launcher,java中的ExtClassLoader,因為是內(nèi)部類,所以編譯后變成(第2個)Launcher$ExtClassLoader,并設(shè)定其parent為null,代表父載入器為Bootstrap。接著Bootstarp又載入Launcher中的AppClassLoader(第三個),Launcher¥AppClassLoader,并設(shè)定parent為之前的ExtClassLoader實例。
3.獲取Class的ClassLoader
Class類是一個樣板,實例就是樣板產(chǎn)生的實體,在Java中,每個類都繼承了Object,Object里的getClass方法,就是用來取得某個特定實體所屬類的參考,它指向一個Class.class的實例,Class本身構(gòu)造函數(shù)是私有的,所以不能自己產(chǎn)生實例,它是在類第一次載入內(nèi)存的時候建立的,每個實例內(nèi)部都會記錄這些個Class的位置:
Class的實例是在類第一次載入內(nèi)存的時候建立的
(關(guān)于Class實例的內(nèi)存分布,可以參考我的GC文章)
基本上,我們可以把每個Class的實體,當做是某個類,在內(nèi)存中的代理人,每次我們需要查詢某個類的資料,比如field、method,就可以使用它,事實上,Java的反射機制,就大量的利用Class類,它的大部分方法都是本地方法。通過Class的newInstance方法,可以得到一個實例。
Java中,每個Class都是由一個ClassLoader實體來載入的,每個Class實體中,都記錄它的ClassLoader的實體,(如果是null,并不是代表不是沒有ClassLoader,而是代表他是由bootstrap loader,或者叫root loader載入的,因為他們不是java寫的,所以沒有實體)。
只要取得Class的實例就可以通過getClassLoader取得對應(yīng)的ClassLoader,
ClassLoader cl=Vector.class.getClassLoader();
class c=cl.loadClass("Hello");
c.newInstance();
這種方式也不會初始化靜態(tài)代碼塊,和Class.forName第二個參數(shù)為false幾乎是一樣的。
4.幾種載入Class的方法
雖然我們知道ClassLoader用來載入Class,但是我們調(diào)用Class.forName()的時候以及new的時候是怎么載入的?
4.1 new Object()
class A(){
public A(){
new B();
}
}
當A里面new B的時候發(fā)生了什么?Class A一定是先載入了,不管是AppClassLoader還是自定義的UrlClassLoader,那么在A new B的時候,會按照以下順序來確定B使用的ClassLoader:(參看 jvm spc5.3)
1.The Java virtual machine uses one of three procedures to create class or interface C denoted by N
:
1.1If N
denotes a nonarray class or an interface, one of the two following methods is used to load and thereby create C:
1.2Otherwise N
denotes an array class. An array class is created directly by the Java virtual machine (§5.3.3), not by a class loader. However, the defining class loader of D is used in the process of creating array class C.
如果B不是數(shù)組,,那么采用A的加載器加載(Boot或者用戶自定義加載器)
如果B是數(shù)組,那活在虛擬機直接創(chuàng)建代表數(shù)組的class,而不是通過classloader載入什么的,當然從使用角度來看,B還是由A的加載器加載的
4.2 Class.forName()
其實,在使用JDBC的時候,就是使用Class.forName來動態(tài)裝入類別的,Class有兩個forName方法:
Public static Class forName(String className)
Public static Class forName(String name,boolean initializa,ClassLoader loader)
如果沒有傳入ClassLoader,那么誰使用ClassLoader.getCallerClassloader,我的理解其實就是調(diào)用它的當前類加載器
最終,他們都是鏈接到了原生方法: (所以他使用的ClassLoader還是比較明確的,可以自定義也可以使用當前類加載器)
private static native Class forName0(String name, boolean initialize,ClassLoader loader) throws ClassNotFoundException;
一種說法,靜態(tài)初始化塊在類第一次載入的時候才會被調(diào)用僅一次,可是實際上當我們第二個參數(shù)為false的時候,只會在第一次載入類,但是不會調(diào)用靜態(tài)初始化塊,而是等到第一次實例化的時候,靜態(tài)初始化才會被調(diào)用。
4.3 ClassLoader.loadClass()
所有的loader都是ClassLoader的子類,他的loadClsss方法就可以用來真正找到類,他的大致流程如下:
4.4 ClassLoader.defineClass
有些時候并不是知道了ClassName,而是得到了一個byte數(shù)組(一般是網(wǎng)絡(luò)傳輸過來的情況,取代了從磁盤讀取字節(jié)流的形式),這時候就可以調(diào)用defineClass方法直接得到Class
4.5 unsafe.defineClass
在一些序列化傳輸中,會直接調(diào)用readObject來讀取對象,但是對象序列化本身只帶一些實例和類描述信息,還是需要在接收端有對應(yīng)的Class,序列化的格式參考:java默認序列化格式
序列化信息中包含了是哪個class名,
private ObjectStreamClass readNonProxyDesc(boolean unshared)
throws IOException {
......
.....
ObjectStreamClass readDesc = null;
try {
readDesc = readClassDescriptor();
} catch (ClassNotFoundException ex) {
throw (IOException) new InvalidClassException(
"failed to read class descriptor").initCause(ex);
}
Class cl = null;
ClassNotFoundException resolveEx = null;
bin.setBlockDataMode(true);
try {
if ((cl = resolveClass(readDesc)) == null) {
resolveEx = new ClassNotFoundException("null class");
}
} catch (ClassNotFoundException ex) {
resolveEx = ex;
}
..........
}
可以看到從序列化信息中,讀取到的是Class的一些描述信息,ObjectStreamClass,他包含必須的自我發(fā)現(xiàn)的一些必要信息:http://docs.oracle.com/javase/6/docs/api/java/io/ObjectStreamClass.html
可以覆蓋次方法,根據(jù)className,從遠程或者其他地方獲取類信息。
5.自定義ClassLoader
URLClassLoader的實例來幫我們載入所需要的類,載入之前,告訴URLClassLoader去哪個地方尋找類,URL可以指向網(wǎng)絡(luò)上的任何位置
TODO
6.被載入多次的類
參考下面的代碼:
URL url=new URL("file:/C:/jar1.jar");
URLClassLoader ucl=new URLClassLoader(new URL[]{url});
Class c=ucl.loadClass("bhsc.hello.TestAction");
ActionInterface a1=(ActionInterface)c.newInstance();
a1.action();
//,Thread.currentThread().getContextClassLoader()
URL url1=new URL("file:/C:/jar1.jar");
URLClassLoader u2=new URLClassLoader(new URL[]{url1});
Class c1=u2.loadClass("bhsc.hello.TestAction");
c.newInstance();
ActionInterface a2=(ActionInterface)c1.newInstance();
a2.action();
System.out.println(ClassLoaderTest.class.getClassLoader());
System.out.println(ActionInterface.class.getClassLoader());
System.out.println(ucl.getClass().getClassLoader());
System.out.println(c.getClass().getClassLoader());
System.out.println(c.getClassLoader());
System.out.println(a1.getClass().getClassLoader());
System.out.println("==============================");
System.out.println(u2.getClass().getClassLoader());
System.out.println(c1.getClass().getClassLoader());
System.out.println(c1.getClassLoader());
System.out.println(a2.getClass().getClassLoader());
結(jié)果:
static init
TestAction
static init
TestAction
sun.misc.Launcher$AppClassLoader@19821f
sun.misc.Launcher$AppClassLoader@19821f
null
null
java.net.URLClassLoader@c17164
java.net.URLClassLoader@c17164
==============================
null
null
java.net.URLClassLoader@ca0b6
ActionInterface是一個接口,TestAction是實現(xiàn)類,項目不能引用到TestAction(如果能夠引用到TestAction的話又是另一種結(jié)果,待會再說)。
可以從結(jié)果看到,類的static塊被調(diào)用了兩次,也就是說在一個虛擬機中,相同的類被載入了兩次,
ClassLoaderTest被AppClassLoader(又稱為SystemLoader,系統(tǒng)載入器)所載入,URL和URLCLassLoader,為null,表示由BootstrapLoader載入,它不是java寫的,所以沒有實例。兩個TestAction,分別由不同的ClassLoader實例載入。