第一部分:Java虛擬機器啟動時,關於類載入方面的一些動作當使用java ProgramName.class運行程式時,Java找到JRE,接著找到jvm.dll,把該動態庫載入記憶體,這就是JVM。然後載入其它動態庫, 並啟用JVM。JVM啟用之後會進行一些初始化工作,之後產生BootstrapLoader,該Class Loader是由C++寫的。BootstrapLoader載入Launcher.java中的ExtClassLoader,並設定其Parent為 null,意思是ExtClassLoader的Parent Class Loader就是BootstrapLoader。然後BootstrapLoader又載入Launcher.java中的
AppClassLoade,並設定其Parent Class Loader是ExtClassLoader。不過如果調用ExtClassLoader的getParent( )方法,則返回的是null。這兩個Class Loader都是以靜態類的形式存在,並且都是用Java編寫的。這三個Class Loader它們都有自己的類尋找路徑:BootstrapLoader: sun.boot.class.pathExtClassLoader: java.ext.dirsAppClassLoader: java.class.path以上三個路徑都是Java的系統屬性,可以通過System.getProperty(String key)方法來查看其設定:System.out.println(System.getProperty("java.class.path")); 現在看看每一種屬性輸出後的值,從而可以發現每種Class Loader分別負責哪些類的裝載:key: sun.boot.class.pathString: %JAVA_HOME%/lib/resources.jar, rt.jar, sunrsasign.jar, jsse.jar, jce.jar, charsets.jar, %JAVA_HOME%/classes key: java.ext.dirsString: %JAVA_HOME%/lib/ext, %Windows%/sun/java/lib/ext key: java.class.pathString: 程式的入口檔案類所在的目錄 由此可見,BootstrapLoader負責Java核心類(所有以java.*開頭的類)。ExtClassLoader負責載入擴充類(所以以javax.*開頭的類以及存在ext目錄下的類)。AppClassLoader負責載入應用程式自身的類。
第二部分:Java的類載入機制。Java是如何載入類的,其流程。類載入按照載入時機,是否自動載入分為兩種:積極式載入和按需載入。積極式載入的類是在JVM啟動之後,應用程式運行之前。至少包含rt.jar中的所有類。按需載入則是在應用程式運行之後,在程式運行過程中,JVM遇到一個還未被裝載的類,這時由Class Loader把該類載入記憶體。 類載入按照方式來分,也是兩種:隱式載入和明確式載入。隱式載入是通過new的方式,在類初始化時由JVM根據相應的Class Loader將類載入。明確式載入則是程式員在代碼中顯式利用某個Class Loader將類載入。 JVM自動裝載類的演算法是這樣的:如果Class A的執行個體引用了Class B的執行個體,則在預設情況下,JVM會先找到Class A的Class Loader,然後用該Class Loader來裝載Class B。 Class Loader裝載類的一般演算法如下:Background: Class Loader是按照層次關係組織起來的,每一個Class Loader都有一個Parent。如果在建立Class Loader時不顯式指定其父Class Loader,JVM會把系統Class Loader指定為該Class Loader的Parent。每一個Class Loader都有自己對應的Loaded Class Cache,換句話說,Loaded Class Cache由兩部分組成:ClassLoader,以及由它載入的Class類名。
- 檢查這個類是否已經被載入進去了
- 如果還沒有載入,調用父物件載入該類
- 如果父物件無法載入,調用本對象的findClass()取得這個類。
所以當建立自己的Class Loader時,只需要重載findClass()方法。Java 1.2之後,類的裝載採用委託模式。
一個已經載入的類是無法被更新的,如果試圖用同一個ClassLoader再次載入 同一個類,會得到異常java.lang.LinkageError: duplicate class definition。只能夠重新建立一個新的ClassLoader執行個體來再次載入新類。
第三部分:定義自己的Class Loader為什麼要使用自己的ClassLoader?
因為JVM內建的ClassLoader只是懂得從本地檔案系統載入標準的java class檔案,如果編寫自己的ClassLoader,可以
- 在執行非置信代碼之前,自動驗證數位簽章
- 動態地建立符合使用者特定需要的定製化構建類
- 從特定的場所取得java class,例如資料庫和網路。
當建立自己的ClassLoader時,需要繼承java.lang.ClassLoader或者它的子類。在執行個體化每個ClassLoader對 象時,需要指定一個父物件;如果沒有的話,系統自動指定ClassLoader.getSystemClassLoader()為父物件。
第四部分:程式中顯示載入並執行個體化類的幾種方式:1) 使用Class類Class foo = Class.forName(String ClassTypeName); // 通過調用ClassLoa der.getCallerClassLoader( )得到當前Class Loader,然後尋找並載入ClassTypeName。orClass foo = Class.forName(String ClassTypeName, boolean initialize, ClassLoader loader) // 顯式指定用哪個Class Loader來尋找並載入ClassTypeName。 ClassTypeName boo = (ClassTypeName) foo.newInstance( ); 2) 通過ClassLoader的子類針對不同情況裝載類,比如java.net.URLClassLoader等。 擷取當前ClassLoader的方法:ClassLoader foo = Thread.currentTread().getCoontextClassLoader();
第五部分: Class.forName()與ClassLoader.loadClass()的區別
Class clazz = Class.forName("XXX.XXX");
與
ClassLoader cl = Thread.currentThread().getContextClassLoader();
Class clazz = cl.loadClass("XXX.XXX");
都可以裝載一個類那麼他們的區別是什麼呢?
進一步研究Class.forName()是調用
Class.forName(name, initialize, loader); 也就是Class.forName("XXX.XXX"); 等同與 Class.forName("XXX.XXX", true, CALLCLASS.class.getClassLoader());
第二次參數表示裝載類的時候是否初始化該類, 即調用類的靜態塊的語句及初始化靜態成員變數。
Class clazz = cl.loadClass("XXX.XXX"); 沒有指定是否初始化的選項。只有執行clazz.newInstance();時才能夠初始化類。可以說 Class.forName("XXX.XXX", false, cl)執行過程是一致的。只是ClassLoader.loadClass()是更底 層的操作。
看一下JDBC驅動的裝載。
Class.forName("com.mysql.jdbc.Driver");
Connection conn = DriverManager.getConnection("jdbcurl");
當調用Class.forName("com.mysql.jdbc.Driver");是Driver已經被初始化並註冊到DriverManager中。MySQL Driver的代碼
public class Driver extends NonRegisteringDriver
implements java.sql.Driver
{
public Driver()
throws SQLException
{
}
static
{
try
{
DriverManager.registerDriver(new Driver());
}
catch(SQLException E)
{
throw new RuntimeException("Can't register driver!");
}
}
}
改修JDBC驅動的裝載
ClassLoader cl = Thread.currentThread().getContextClassLoader();
Class clazz = cl.loadClass("com.mysql.jdbc.Driver");
clazz.newInstance();
Connection conn = DriverManager.getConnection("jdbcurl");
同樣可以執行。
進一步說:
Class .forName是從指定的classloader中裝載類,如果沒有指定,也就是一個參數的時候,是從裝載當前對象執行個體所在的classloader中裝載類.
而ClassLoader的執行個體調用loadclass方法,是指從當前ClassLoader執行個體中調用類,而這個執行個體與裝載當前所在類執行個體的Classloader也許不是同一個 .
舉個例子吧, 有A,B , C兩個ClassLoader , 當前啟動並執行類D的執行個體是d(裝載它的是A) , 如果D中使用Class.forName那麼就是使用的ClassLoader就是A,當然,也可以指定為B. 而如果D中代碼找到的ClassLoader執行個體是C,那麼就是用D來裝載所指定的類.
為什麼要用不同的ClassLoader 裝載?
舉例來說:如果在Class 被載入的過程中,你希望使用在自己的Class Loader來實現特定的操作,請使用ClassLoader方式。
貌似CGLib之類的bytecode generation架構很多地方會使用指定特殊ClassLoader的方式。
使用多個classloader的情況非常常見,比如說我們的app server,那麼都是這樣的. 在Web與EJB間, 他們的classLoader就是不同的,這樣做的目的就是為了避免兩者間類裝載的相互幹擾.
再舉個例子:
Static初始化區塊在什麼時候被調用的問題?
Public A{Static{System.out.println(“HaHaHa”);}}
Class.forName(“A”);
Class.forName(“A”,false, ClassLoader.getSystemClassLoader());
看看奧妙在哪裡?Java ClassLoader機制和原理又是如何?
程式樣本:
public class A {
static { System.out.println("A`static is executed!");}
public A() {System.out.println("A`construct is executed!");}
public void show(){System.out.println("A`method is executed!");}
}
調用程式1:
Class c = Class.forName("A");
Method m = c.getMethod("show", new Class[0]);
System.out.println("A`test is executed!");
Object obj = c.newInstance();
m.invoke(obj, new Object[0]);
執行結果:
A`static is executed!
A`test is executed!
A`construct is executed!
A`method is executed!
調用程式2:
Class c = ClassLoader.getSystemClassLoader().loadClass("A");
System.out.println("A`test is executed!");
Method m = c.getMethod("show", new Class[0]);
Object obj = c.newInstance();
m.invoke(obj, new Object[0]);
執行結果:
A`test is executed!
A`static is executed!
A`construct is executed!
A`method is executed!
可見執行順序為先執行 static{}塊中的代碼,然後執行建構函式,之後才是方法的調用。
classloader的兩種載入方式:
1)pre-loading預先載入,載入基礎 類
2)load-on-demand按需求載入
java動態載入class的兩種方式:
1)implic it隱式,即利用執行個體化才載入的特性來動態載入class
2)explic it顯式方式,又分兩種方式:
a)java.lang.Class的forName()方法 (上述調用程式1採用此方式載入)
b)java.lang.ClassLoader的loadClass()方法(上述調用程式2採用此方式載入)
static塊在什麼時候執行?
當調用forName(String)載入class時執行,( 這個過程在類的所有父類中遞迴地調用)
如果調用ClassLoader.loadClass並不會執行.
forName(String,false,ClassLoader)時也不會執行.
如果載入Class時沒有執行static塊則在第一次執行個體化時執行.比如new ,Class.newInstance()操作
static塊僅執行一次