類加載器詳解


  類加載器是負責將可能是網絡上、也可能是磁盤上的class文件加載到內存中。並為其生成對應的java.lang.class對象。一旦一個類被載入JVM了,同一個類就不會被再次加載。那么怎樣才算是同一個類?在JAVA中一個類用其全限定類名(包名和類名)作為其唯一標識,但是在JVM中,一個類用其全限定類名和其類加載器作為其唯一標識。也就是說,在JAVA中的同一個類,如果用不同的類加載器加載,則生成的class對象認為是不同的。

  當JVM啟動時,會形成由三個類加載器組成的初始類加載器層次結構

1、啟動類加載器BootstrapClassLoader:

    是嵌在JVM內核中的加載器,該加載器是用C++語言寫的,主要負載加載JAVA_HOME/lib下的類庫,啟動類加載器無法被應用程序直接使用。

2、擴展類加載器Extension ClassLoader:

     該加載器器是用JAVA編寫,且它的父類加載器是Bootstrap,是由sun.misc.Launcher$ExtClassLoader實現的,主要加載JAVA_HOME/lib/ext目錄中的類庫。開發者可以這幾使用擴展類加載器。

  我們知道java中系統屬性java.ext.dirs指定的目錄由ExtClassLoader加載器加載,如果程序中沒有指定該系統屬性(-Djava.ext.dirs=sss/lib)那么該加載器默認加載$JAVA_HOME/lib/ext目錄下的所有jar文件,通過程序來看下系統變量java.ext.dirs所指定的路徑:

public class Test
{
    public static void main(String[] args)
    {
        System.out.println(System.getProperty("java.ext.dirs"));
    }
}

執行結果:

C:\Program Files (x86)\Java\jdk1.6.0_43\jre\lib\ext;C:\Windows\Sun\Java\lib\ext

3、系統類加載器App ClassLoader:

系統類加載器,也稱為應用程序類加載器,負責加載應用程序classpath目錄下的所有jar和class文件。它的父加載器為Ext ClassLoader。

public class Test
{
    public static void main(String[] args)
    {
        System.out.println(ClassLoader.getSystemClassLoader());
    }
}

執行結果:

sun.misc.Launcher$AppClassLoader@addbf1

程序中的方法是返回委托的系統類加載器,通過執行結果,可以知道,系統類加載器是通過sun.misc.Launcher$AppClassLoader實現的。

上述三種類加載器的層次關系如下:

注意:類加載器的體系並不是“繼承”體系,而是委派體系,大多數類加載器首先會到自己的parent中查找類或者資源,如果找不到才會到自己本地查找。類加載器的委托行為動機是為了避免相同的類被加載多次。

我們可以通過程序來驗證下:

    public static void main(String[] args)
    {
        System.out.println(ClassLoader.getSystemClassLoader().getParent());
    }

執行結果:

sun.misc.Launcher$ExtClassLoader@42e816

在這里可以看到,Application ClassLoader的父加載器確實是ExtClassLoader。

我們在往上走一層,如果猜想沒錯的話,ExtClassLoader的父加載器應該是BootStrap ClassLoader

public static void main(String[] args)
    {
        System.out.println(ClassLoader.getSystemClassLoader().getParent().getParent());
    }

執行結果:

null

這里不是說ExtClassLoader沒有父加載器,而是因為Bootstrap ClassLoader使用C++寫的。  

 

 

UML類圖:

我們解讀源碼來看下這些類的繼承關系

AppClassLoader與ExtClassLoader都是Lancher的內部類:

static class ExtClassLoader extends URLClassLoader
static class AppClassLoader extends URLClassLoader
public class URLClassLoader extends SecureClassLoader
public class SecureClassLoader extends ClassLoader
public abstract class ClassLoader

我們有這樣一個結論,除了啟動類加載器Bootstrap ClassLoader,其他的類加載器都是ClassLoader的子類。

我們來反編譯看下rt.jar,在sun.misc.Launcher$AppClassLoader路徑中,看下AppClassLoader的源碼:

 1 static class AppClassLoader extends URLClassLoader
 2   {
 3     public static ClassLoader getAppClassLoader(ClassLoader paramClassLoader)
 4       throws IOException
 5     {
 6       String str = System.getProperty("java.class.path");
 7       File[] arrayOfFile = (str == null) ? new File[0] : Launcher.access$200(str);
 8 
 9       return ((AppClassLoader)AccessController.doPrivileged(new PrivilegedAction(str, arrayOfFile, paramClassLoader)
10       {
11         public Object run() {
12           URL[] arrayOfURL = (this.val$s == null) ? new URL[0] : Launcher.access$300(this.val$path);
13 
14           return new Launcher.AppClassLoader(arrayOfURL, this.val$extcl);
15         }
16       }));
17     }

1、在第6行代碼中可以看到,系統類加載器只能加載java.class.path路徑下的class文件。我們通過程序看下java.class.path指定的路徑

1 public class Test
2 {
3     public static void main(String[] args)
4     {
5         System.out.println(System.getProperty("java.class.path"));
6     }
7 }

執行結果:

如果是JAVA工程:

F:\workSpace\test\bin

如果是JAVAWEB工程:

F:\workSpace\study\WebRoot\WEB-INF\classes

雙親委派模型

如果一個類加載器收到了一個類加載請求,它不會自己去嘗試加載這個類,而是把這個請求轉交給父類加載器去完成。每一個層次的類加載器都是如此。因此所有的類加載請求都應該傳遞到最頂層的啟動類加載器中,只有到父類加載器反饋自己無法完成這個加載請求(在它的搜索范圍沒有找到這個類)時,子類加載器才會嘗試自己去加載。委派的好處就是避免有些類被重復加載。

雙親委派的實現比較簡單,我們來看下源碼:

 

 1 protected synchronized Class<?> loadClass(String paramString, boolean paramBoolean)
 2     throws ClassNotFoundException
 3   {
//檢查是否被加載過
4 Class localClass = findLoadedClass(paramString);
//如果沒有加載,則調用父類加載器
5 if (localClass == null) { 6 try {
//父類加載器不為空
7 if (this.parent != null) 8 localClass = this.parent.loadClass(paramString, false); 9 else {
//父類加載器為空,則使用啟動類加載器
10 localClass = findBootstrapClass0(paramString); 11 } 12 } 13 catch (ClassNotFoundException localClassNotFoundException) 14 {
//如果父類加載失敗,則使用自己的findClass方法進行加載
15 localClass = findClass(paramString); 16 } 17 } 18 if (paramBoolean) { 19 resolveClass(localClass); 20 } 21 return localClass; 22 }

 

先檢查是否已經被加載過,若沒有加載則調用父類加載器的loadClass方法,若父類加載器不存在,則使用啟動類加載器。如果父類加載器加載失敗,則拋出異常之后看,再調用自己的findClass方法進行加載。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM