1.java源碼和class文件Class java源碼都被編譯為class文件,,jvm真正啟動之后還是要找到對應的Class,,靠的就是ClassLoader
2.啟動以及三個ClassLoader 當我們通過命令java XXMain.class的時候,java.exe根據(jù)我們的設置找到了JRE,,接著找到JRE鐘的jvm.dll,,真正的java虛擬機,最后載入這個動態(tài)函數(shù)庫,,啟動Java虛擬機,虛擬機一啟動,,首先做一些初始化的動作,,獲得系統(tǒng)參數(shù)等,初始化完成后,,就會產生第一個ClassLoader,,即Bootstrap Loader,這個Loader除了初始化動作之外,,最重要的就是載入定義在sun.misc命名空間下的Launcher,java中的ExtClassLoader,,因為是內部類,所以編譯后變成(第2個)Launcher$ExtClassLoader,,并設定其parent為null,,代表父載入器為Bootstrap,。接著Bootstarp又載入Launcher中的AppClassLoader(第三個),Launcher¥AppClassLoader,,并設定parent為之前的ExtClassLoader實例,。 3.獲取Class的ClassLoader Class類是一個樣板,,實例就是樣板產生的實體,,在Java中,每個類都繼承了Object,,Object里的getClass方法,,就是用來取得某個特定實體所屬類的參考,它指向一個Class.class的實例,,Class本身構造函數(shù)是私有的,,所以不能自己產生實例,它是在類第一次載入內存的時候建立的,,每個實例內部都會記錄這些個Class的位置:
Class的實例是在類第一次載入內存的時候建立的 (關于Class實例的內存分布,,可以參考我的GC文章)
基本上,我們可以把每個Class的實體,,當做是某個類,,在內存中的代理人,每次我們需要查詢某個類的資料,,比如field,、method,就可以使用它,,事實上,,Java的反射機制,就大量的利用Class類,,它的大部分方法都是本地方法,。通過Class的newInstance方法,可以得到一個實例,。
Java中,,每個Class都是由一個ClassLoader實體來載入的,每個Class實體中,,都記錄它的ClassLoader的實體,,(如果是null,并不是代表不是沒有ClassLoader,,而是代表他是由bootstrap loader,,或者叫root loader載入的,,因為他們不是java寫的,所以沒有實體),。 只要取得Class的實例就可以通過getClassLoader取得對應的ClassLoader,,
ClassLoader cl=Vector.class.getClassLoader(); class c=cl.loadClass("Hello"); c.newInstance();
這種方式也不會初始化靜態(tài)代碼塊,和Class.forName第二個參數(shù)為false幾乎是一樣的,。
4.幾種載入Class的方法 雖然我們知道ClassLoader用來載入Class,,但是我們調用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.1.1If D was defined by the bootstrap class loader, then the bootstrap class loader initiates loading of C (§5.3.1). 1.1.2If D was defined by a user-defined class loader, then that same user-defined class loader initiates loading of C (§5.3.2).
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,我的理解其實就是調用它的當前類加載器
最終,,他們都是鏈接到了原生方法: (所以他使用的ClassLoader還是比較明確的,,可以自定義也可以使用當前類加載器) private static native Class forName0(String name, boolean initialize,ClassLoader loader) throws ClassNotFoundException;
一種說法,靜態(tài)初始化塊在類第一次載入的時候才會被調用僅一次,,可是實際上當我們第二個參數(shù)為false的時候,,只會在第一次載入類,但是不會調用靜態(tài)初始化塊,,而是等到第一次實例化的時候,,靜態(tài)初始化才會被調用。
4.3 ClassLoader.loadClass() 所有的loader都是ClassLoader的子類,,他的loadClsss方法就可以用來真正找到類,他的大致流程如下:
4.4 ClassLoader.defineClass 有些時候并不是知道了ClassName,,而是得到了一個byte數(shù)組(一般是網絡傳輸過來的情況,,取代了從磁盤讀取字節(jié)流的形式),這時候就可以調用defineClass方法直接得到Class
4.5 unsafe.defineClass
api:
sun.misc..defineClass(
arg0, byte[] arg1, int arg2, int arg3,
arg4,
arg5) 使用: unsafe.defineClass(className, btraceCode, 0, btraceCode.length, classLoader, null);
這種方式可以得到Class,,而且還可以指定classloader
4.6 ObjectInputStream.readObject()在一些序列化傳輸中,,會直接調用readObject來讀取對象,,但是對象序列化本身只帶一些實例和類描述信息,還是需要在接收端有對應的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可以指向網絡上的任何位置
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());
結果:
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
java.net.URLClassLoader@ca0b6
ActionInterface是一個接口,TestAction是實現(xiàn)類,,項目不能引用到TestAction(如果能夠引用到TestAction的話又是另一種結果,,待會再說)。
可以從結果看到,,類的static塊被調用了兩次,,也就是說在一個虛擬機中,相同的類被載入了兩次,,
ClassLoaderTest被AppClassLoader(又稱為SystemLoader,,系統(tǒng)載入器)所載入,URL和URLCLassLoader,,為null,,表示由BootstrapLoader載入,它不是java寫的,,所以沒有實例,。兩個TestAction,分別由不同的ClassLoader實例載入,。
|