天天看點

Java 類加載器

1). 位元組碼和.class檔案差別

建立Java對象時,JVM将這個對象對應的位元組碼加載到記憶體中,這個位元組碼的原始資訊存放在classpath目錄下(及Java工程的bin目錄下)的.class檔案中,類加載需要将.class檔案導入硬碟,經過處理變成位元組碼加載到記憶體。

示例:

public class ClassLoaderTest {
    public static void main(String[] args) {
        // 輸出ClassLoaderTest的類加載器名稱
        System.out.println("ClassLoaderTest類的類加載器名稱:" + ClassLoaderTest.class.getClassLoader().getClass().getName());
        System.out.println("System類的類加載器名稱:" + System.class.getClassLoader());
        System.out.println("List類的類加載器名稱:" + List.class.getClassLoader());
        
        // 擷取ClassLoaderTest類的類加載器
        ClassLoader classLoader = ClassLoaderTest.class.getClassLoader();
        while (null != classLoader) {
            System.out.println(classLoader.getClass().getName() + "->");
            // 擷取類加載器的父類
            classLoader = classLoader.getParent();
        }
        System.out.println(classLoader);        
    }
}
           

列印結果:

Java 類加載器

圖1.png

從列印結果可以看出ClassLoaderTest類是有AppClassLoader加載的。

因為System類,List,Map等這樣的系統提供jar類都在rt.jar中,是以由BootStrap類加載器加載,因為BootStrap是祖先類,不是Java編寫的,是以列印出class為null。詳情請看第三點:類加載器的委托機制

2). Java虛拟中的類加載器

Java虛拟機中可以安裝多個類加載器,系統預設的有三個,每個類負責加載特定位置的類:BootStrap,ExtClassLoader,AppClassLoader

由于類加載器本身是Java類,也需要被類加載器加載,是以必須有一個為類加載器不是Java類,是以Bootstrap是由C/C++代碼編寫,被封裝到JVM核心中,ExtClassLoader和AppClassLoader是Java類。

類加載器屬性結構圖:

Java 類加載器

圖2.png

3). 類加載器的委托機制

問題:Java虛拟機要加載第一個類的時候,使用哪一個類加載器加載?

(1). 目前線程的類加載器加載線程中的第一個類(目前線程的類加載器:Thread類中有一個get/setContextClassLoader(ClassLoader);方法,可以擷取/指定本線程中的類加載器)

(2). 如果類A引用了類B,Java虛拟機将使用加載類A的加載器來加載類B

(3). 可以直接調用ClassLoader.loadClass(String className)方法來指定某個類加載器去加載某個類

每個類加載器加載類時,先委托其上級類加載器來加載,當所有父加載器沒有加載到類時,回到發起者類加載器,如果還是加載不了,則會抛出ClassNotFoundException。

好處:

能夠很好的統一管理類加載,首先交給上級,如果上級有,就加載,這樣如果之前已經加載過的類,這時候在來加載它的時候隻要拿過來用就可以了,無需二次加載

4). 測試ExtClassLoader

  • 将ClassLoaderTest類打包為Jar檔案,拷貝至

    jdk\jre\lib\ext\下

    路徑
    Java 類加載器
    圖3.png
  • 接下來在eclipse中運作ClassLoaderTest檔案,其列印結果為
    Java 類加載器

    圖4.png

    列印結果驗證了類加載器的委托機制.

    測試完成後記得将test.jar删除

5). 類加載器主要方法

  • 預設的類加載器
System.out.println("預設的類加載器:"+ClassLoader.getSystemClassLoader());  
           
Java 類加載器

圖5.png

  • loadClass(String name) 源碼
public Class<?> loadClass(String name) throws ClassNotFoundException {
        return loadClass(name, false);
    }

    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不為null,就調用parent的loadClass進行類加載
                    if (parent != null) {
                        c = parent.loadClass(name, false);
                    } else {
                        // 如果自定義的類加載器的parent為null,就調用findBootstrapClassOrNull方法查找類,就是BootStrap類加載器
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // 類未發現異常
                }

                if (c == null) {
                    // 如果仍然沒有發現,就調用自己的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;
        }
    }
           
  • defineClass,将class檔案的位元組數組編譯成一個class對象,不能重寫,内部實作為C/C++代碼
    Java 類加載器
    圖6.png

6). 自定義ClassLoader

  • 定義一個被加載的類
/**
 * 加載類
 * @author mazaiting
 */
public class ClassLoaderAttachment extends Date{
    private static final long serialVersionUID = -1892974244318329380L;
    
    @Override
    public String toString() {
        return "Hello ClassLoader";
    }
}
           
  • 自定義ClassLoader
/**
 * 自定義類加載器
 * 步驟:
 *  1. 繼承ClasssLoader
 *  2. 重寫findClass(String name) 方法
 * @author mazaiting
 */
public class MyClassLoader extends ClassLoader{
    /**需要加載類.class檔案的目錄*/
    private String classDir;
    /**無參構造*/
    public MyClassLoader() {}
    public MyClassLoader(String classDir) {
        this.classDir = classDir;
    }
    
    // 測試,先将ClassLoaderAttachment.class檔案加密到工程的class_temp目錄下
    public static void main(String[] args) throws Exception {
        // 配置運作參數
        // ClassLoaderAttachment.class原路徑--E:\test\java_advanced\ClassLoader\bin\com\mazaiting\ClassLoaderAttachment.class 
        String srcPath = args[0];
        // ClassLoaderAttachment.class輸出路徑--E:\test\java_advanced\ClassLoader\class_temp\
        String desPath = args[1];
        // 擷取檔案名
        String desFileName = srcPath.substring(srcPath.lastIndexOf("\\") +1);
        // 配置目标檔案路徑
        String desPathFile = desPath + "/" + desFileName;
        FileInputStream fis = new FileInputStream(srcPath);
        FileOutputStream fos = new FileOutputStream(desPathFile);
        // 将class進行加密
        encodeAndDecode(fis, fos);
        fis.close();
        fos.close();
    }
    
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        // class檔案的路徑
        String classPathFile = classDir + "/" + name + ".class";
        try {
            // 将class檔案進行解密
            FileInputStream fis = new FileInputStream(classPathFile);
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            encodeAndDecode(fis, baos);
            byte[] classByte = baos.toByteArray();
            // 将位元組流變成一個class
            return defineClass(null, classByte, 0, classByte.length);
        } catch (Exception e) {
            e.printStackTrace();
        }
        
        return super.findClass(name);
    }
    
    /**
     * 加密解密
     * @param is 輸入流
     * @param os 輸出流
     * @throws IOException 
     */
    private static void encodeAndDecode(InputStream is, OutputStream os) throws IOException {
        int len;
        while ((len = is.read()) != -1) {
            len ^= 0xFF;
            os.write(len);
        }
    }
}
           
  • 運作:在MyClassLoader的main方法右鍵 -> Run As -> Run Configurations...,進行如下配置(配置前請確定ClassLoader工程目錄下存在class_temp檔案夾,其中第一個參數是ClassLoaderAttachment.class檔案的源路徑,第二個參數是加密後存放的目錄),點選Run運作,此時在

    E:\test\java_advanced\ClassLoader\class_temp

    将會生成加密後的

    ClassLoaderAttachment.class

    檔案
    Java 類加載器
    圖7.png
  • 使用,在

    ClassLoaderTest

    的main函數中應用
public class ClassLoaderTest {
    public static void main(String[] args) {
        try {
            // 加載類
            Class classDate = new MyClassLoader("class_temp").loadClass("ClassLoaderAttachment");
            // 建立執行個體
            Date date = (Date) classDate.newInstance();
            // 輸出ClassLoaderAttachment類的加載器名稱
            System.out.println("ClassLoader: " + date.getClass().getClassLoader().getClass().getName());
            // 調用方法
            System.out.println(date.toString());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
           
Java 類加載器

圖8.png

  • 如果loadClass方法的參數為類的全路徑名
// 加載類
    Class classDate = new MyClassLoader("class_temp").loadClass("com.mazaiting.ClassLoaderAttachment");
           
Java 類加載器

圖9.png

  • \class_temp\

    路徑下的ClassLoaderAttachment.class複制到

    \bin\com\mazaiting\

    路徑下,然後運作
Exception in thread "main" java.lang.ClassFormatError: Incompatible magic value 889275713 in class file com/mazaiting/ClassLoaderAttachment
           
Java 類加載器

圖10.png

不合适的魔數錯誤(class檔案的前六個位元組是個魔數用來辨別class檔案的),因為加密後的資料AppClasssLoader無法解析。