classloader工作機制

轉載別人一篇好的文章:http://www.cnblogs.com/xujian2014/p/5551153.html

一、ClassLoader概念

   ClassLoader是用來動態的加載class文件到虛擬機中,並轉換成java.lang.class類的一個實例,每個這樣的實例用來表示一個java類,我們可以根據Class的實例得到該類的信息,並通過實例的newInstance()方法創建出該類的一個對象,除此之外,ClassLoader還負責加載Java應用所需的資源,如圖像文件和配置文件等。

           Classloader除了能將class加載到JVM中之外,還有一個重要的作用就是審查每個類由誰加載,他是一種父優先的等級加載機制。

  ClassLoader類是一個抽象類。如果給定類的二進制名稱,那麼類加載器會試圖查找或生成構成類定義的數據。一般策略是將名稱轉換爲某個文件名,然後從文件系統讀取該名稱的“類文件”。ClassLoader類使用委託模型來搜索類和資源。每個 ClassLoader實例都有一個相關的父類加載器。需要查找類或資源時,ClassLoader實例會在試圖親自查找類或資源之前,將搜索類或資源的任務委託給其父類加載器。  

  注意:程序在啓動的時候,並不會一次性加載程序所要用的所有class文件,而是根據程序的需要,通過Java的類加載機制來動態加載某個class文件到內存中。

二、JVM平臺提供三層classLoader

       Classloader設計的機制是:上級委託接待機制

  1. Bootstrap classLoader:採用native code實現,是JVM的一部分,主要加載JVM自身工作需要的類,如java.lang.*、java.uti.*等; 這些類位於$JAVA_HOME/jre/lib/rt.jar。Bootstrap ClassLoader不繼承自ClassLoader,因爲它不是一個普通的Java類,底層由C++編寫,已嵌入到了JVM內核當中,當JVM啓動後,Bootstrap ClassLoader也隨着啓動,負責加載完核心類庫後,並構造Extension ClassLoader和App ClassLoader類加載器。
  2. ExtClassLoader:擴展的class loader,加載位於$JAVA_HOME/jre/lib/ext目錄下的擴展jar。
  3. AppClassLoader:系統class loader,父類是ExtClassLoader,加載$CLASSPATH下的目錄和jar;它負責加載應用程序主函數類。

  其體系結構圖如下:

  

  如果要實現自己的類加載器,不管是實現抽象列ClassLoader,還是繼承URLClassLoader類,它的父加載器都是AppClassLoader,因爲不管調用哪個父類加載器,創建的對象都必須最終調用getSystemClassLoader()作爲父加載器,getSystemClassLoader()方法獲取到的正是AppClassLoader。

  注意:Bootstrap classLoader並不屬於JVM的等級層次,它不遵守ClassLoader的加載規則,Bootstrap classLoader並沒有子類。

三、JVM加載class文件到內存有兩種方式

  1. 隱式加載:不通過在代碼裏調用ClassLoader來加載需要的類,而是通過JVM來自動加載需要的類到內存,例如:當類中繼承或者引用某個類時,JVM在解析當前這個類不在內存中時,就會自動將這些類加載到內存中。
  2. 顯示加載:在代碼中通過ClassLoader類來加載一個類,例如調用this.getClass.getClassLoader().loadClass()或者Class.forName()

四、ClassLoader加載類的過程

  1. 找到.class文件並把這個文件加載到內存中
  2. 字節碼驗證,Class類數據結構分析,內存分配和符號表的鏈接
  3. 類中靜態屬性和初始化賦值以及靜態代碼塊的執行

 五、自定義類加載器

  1、爲何要自定義類加載器?

  JVM提供的類加載器,只能加載指定目錄的jar和class,如果我們想加載其他位置的類或jar時,例如加載網絡上的一個class文件,默認的ClassLoader就不能滿足我們的需求了,所以需要定義自己的類加載器。

  2、如何實現自定義的類加載器?

  我們實現一個ClassLoader,並指定這個ClassLoader的加載路徑。有兩種方式:

  方式一:繼承ClassLoader,重寫父類的findClass()方法,代碼如下:

複製代碼
 1 import java.io.ByteArrayOutputStream;
 2 import java.io.File;
 3 import java.io.FileInputStream;
 4 import java.io.IOException;
 5 public class PathClassLoader extends ClassLoader
 6 {
 7     public static final String drive = "d:/";
 8     public static final String fileType = ".class";
 9 
10     public static void main(String[] args) throws Exception
11     {
12         PathClassLoader loader = new PathClassLoader();
13         Class<?> objClass = loader.loadClass("HelloWorld", true);
14         Object obj = objClass.newInstance();
15         System.out.println(objClass.getName());
16         System.out.println(objClass.getClassLoader());
17         System.out.println(obj.getClass().toString());
18     }
19 
20     public Class<?> findClass(String name)
21     {
22         byte[] data = loadClassData(name);
23         return defineClass(name, data, 0, data.length);// 將一個 byte 數組轉換爲 Class// 類的實例
24     }
25     public byte[] loadClassData(String name)
26     {
27         FileInputStream fis = null;
28         byte[] data = null;
29         try
30         {
31             fis = new FileInputStream(new File(drive + name + fileType));
32             ByteArrayOutputStream baos = new ByteArrayOutputStream();
33             int ch = 0;
34             while ((ch = fis.read()) != -1)
35             {
36                 baos.write(ch);
37             }
38             data = baos.toByteArray();
39         } catch (IOException e)
40         {
41             e.printStackTrace();
42         }
43         return data;
44     }
45 }
複製代碼

  在第13行,我們調用了父類的loadClass()方法,該方法使用指定的二進制名稱來加載類,下面是loadClass方法的源代碼:

複製代碼
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name))
        {
            // 第一步先檢查這個類是否已經被加載
            Class<?> c = findLoadedClass(name);
            if (c == null)
            {
                long t0 = System.nanoTime();
                try
                {
                    //parent爲父加載器
                    if (parent != null)
                    {
                        //將搜索類或資源的任務委託給其父類加載器
                        c = parent.loadClass(name, false);
                    } else
                    {
                        //檢查該class是否被BootstrapClassLoader加載
                        c = findBootstrapClassOrNull(name);
                    }
                } 
                catch (ClassNotFoundException e)
                {
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                }
                if (c == null)
                {
                    //如果上述兩步均沒有找到加載的class,則調用findClass()方法
                    long t1 = System.nanoTime();
                    c = findClass(name);
                    // this is the defining class loader; record the stats
                    sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    sun.misc.PerfCounter.getFindClasses().increment();
                }
            }
            if (resolve)
            {
                resolveClass(c);
            }
            return c;
        }
    }
複製代碼

  這個方法首先檢查指定class是否已經被加載,如果已被加載過,則調用resolveClass()方法鏈接指定的類,如果還未加載,則先將搜索類或資源的任務委託給其父類加載器,檢查該class是否被BootstrapClassLoader加載,如果上述兩步均沒有找到加載的class,則調用findClass()方法,在我們自定義的加載器中,我們重寫了findClass方法,去我們指定的路徑下加載class文件。

  另外,我們自定義的類加載器沒有指定父加載器,在JVM規範中不指定父類加載器的情況下,默認採用系統類加載器即AppClassLoader作爲其父加載器,所以在使用該自定義類加載器時,需要加載的類不能在類路徑中,否則的話根據雙親委派模型的原則,待加載的類會由系統類加載器加載。如果一定想要把自定義加載器需要加載的類放在類路徑中, 就要把自定義類加載器的父加載器設置爲null。 

  方式二:繼承URLClassLoader類,然後設置自定義路徑的URL來加載URL下的類。

  我們將指定的目錄轉換爲URL路徑,然後重寫findClass方法

六、實現類的熱部署

  1、什麼是類的熱部署?

  所謂熱部署,就是在應用正在運行的時候升級軟件,不需要重新啓用應用。

  對於Java應用程序來說,熱部署就是運行時更新Java類文件。在基於Java的應用服務器實現熱部署的過程中,類裝入器扮演着重要的角色。大多數基於Java的應用服務器,包括EJB服務器和Servlet容器,都支持熱部署。

  類裝入器不能重新裝入一個已經裝入的類,但只要使用一個新的類裝入器實例,就可以將類再次裝入一個正在運行的應用程序。

  2、如何實現Java類的熱部署

  前面的分析,我們已經知道,JVM在加載類之前會檢查請求的類是否已經被加載過來,也就是要調用findLoadedClass方法查看是否能夠返回類實例。如果類已經加載過來,再調用loadClass會導致類衝突。

  但是,JVM判斷一個類是否是同一個類有兩個條件:一是看這個類的完整類名是否一樣(包括包名),二是看加載這個類的ClassLoader加載器是否是同一個(既是是同一個ClassLoader類的兩個實例,加載同一個類也會不一樣)。

  所以,要實現類的熱部署可以創建不同的ClassLoader的實例對象,然後通過這個不同的實例對象來加載同名的類。






發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章