天天看點

Java 類加載機制,雙親委派模型和實作自己的類加載器

類加載可以看這個部落格

http://www.importnew.com/25295.html

雙親委派模型

Java 類加載機制,雙親委派模型和實作自己的類加載器

啟動類加載器(Bootstrap ClassLoader):負責加載 JAVA_HOME\lib 目錄中的,或通過-Xbootclasspath參數指定路徑中的,且被虛拟機認可(按檔案名識别,如rt.jar)的類。

擴充類加載器(Extension ClassLoader):負責加載 JAVA_HOME\lib\ext 目錄中的,或通過java.ext.dirs系統變量指定路徑中的類庫。

應用程式類加載器(Application ClassLoader):負責加載使用者路徑(classpath)上的類庫。

抄的上面那個部落格。。

當一個類加載器收到類加載任務,會先交給其父類加載器去完成,是以最終加載任務都會傳遞到頂層的啟動類加載器,隻有當父類加載器無法完成加載任務時,才會嘗試執行加載任務。

來看類加載的代碼

protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // First, check if the class has already been loaded
            //檢視類是否已經被加載
            Class c = findLoadedClass(name);
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                    if (parent != null) {
                        //父加載器不為空,調用父
                        c = parent.loadClass(name, false);
                    } else {
                        //為空,調用BootStrap加載器
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                }

                if (c == null) {
                    // If still not found, then invoke findClass in order
                    // to find the class.
                    //父類沒有加載成功,自己本身進行加載
                    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;
        }
    }
           

大緻過程

1.檢視這個類是否已經被加載

2.若未加載,則檢視父類加載器是否為空,為空就直接調用啟動類加載器,不為空則調用父類加載器

3.若第二步沒有成功,則自己進行加載, 也就是調用自己的findclass方法

實作自己的類加載器

1.首先寫一個類

package cn.dengbin97.myclass;

public class Solution {

    public static void main(String[] args) {

    }
}
           

2.對類進行編譯,放到一個目錄

//此處由于指定了包,是以不能直接在目前目錄編譯
//package cn.dengbin97.myclass;
//像上面這樣的就要傳回到cn目錄進行編譯
C:\temp\cn>javac C:\temp\cn\dengbin97\myclass\Solution.java
           

3.定義自己的類加載器,重寫findclass方法

若要去破壞雙親委派原則,可以重寫loadclass方法,最好還是不要
package main;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;

public class MyClassLoader extends ClassLoader {

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        try {
            //擷取檔案位元組流
            byte[] bytes = getClassBytes(new File("C:\\temp\\cn\\dengbin97\\myclass\\Solution.class"));
            //把位元組流轉化為Class對象
            Class<?> c = this.defineClass(name, bytes, , bytes.length);
            return c;
        } catch (Exception e) {
            e.printStackTrace();
        }

        return super.findClass(name);
    }

    /**
     * 擷取檔案的位元組流
     * */
    private byte[] getClassBytes(File file) {
        byte[] tempbytes = new byte[];
        byte[] res  = null;
        InputStream in = null;
        try {
            in = new FileInputStream(file);
            //此處為了友善,直接一次讀取了,可以進行改進
            int read = in.read(tempbytes);
            res = new byte[read];
            //把讀取結果拷貝的新的數組,保證新數組不留白,因為後面要用到位元組流的大小,可以從數組擷取
            System.arraycopy(tempbytes, , res, , read);
        } catch (Exception e1) {
            e1.printStackTrace();
        } finally {
            if (in != null) {
                try {
                    in.close();
                } catch (IOException e1) {
                }
            }
        }
        return res;
    }

}
           

4.進行測試

public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
         MyClassLoader loader = new MyClassLoader();     
         //使用Class.forName進行類加載
         //指定類和使用的加載器
         Class<?> clazz = Class.forName("cn.dengbin97.myclass.Solution", true, loader);
         Object newInstance = clazz.newInstance();
         //擷取類執行個體
         System.out.println(newInstance);
         //擷取類
         System.out.println(newInstance.getClass());
         //擷取使用的加載器
         System.out.println(newInstance.getClass().getClassLoader());
    }

//運作結果
cn.dengbin97.myclass.Solution@70dea4e
class cn.dengbin97.myclass.Solution
//此處可以看到,類加載器使用的是我們自己的
main.MyClassLoader@7852e922
           

loadClass:檢查類是否已經加載,調用父類或自己本身進行類的加載

findClass:真正實作類的加載,擷取位元組碼檔案的位元組流,調用defineClass得到class對象

defineClass:将讀取的二進制位元組碼檔案轉化為Class對象