【JVM類加載】第三天自定義類加載器相關內容

二進制名字
在這裏插入圖片描述如java.net.URLClassLoader$3$1 表示URLClassLoader的第三個匿名內部類中的第一個匿名內部類

ClassLoader分析

A class loader is an object that is responsible for loading classes. The class ClassLoader is an abstract class. Given the binary name of a class, a class loader should attempt to locate or generate data that constitutes a definition for the class. A typical strategy is to transform the name into a file name and then read a “class file” of that name from a file system. Every Class object contains a reference to the ClassLoader that defined it. Class objects for array classes are not created by class loaders, but are created automatically as required by the Java runtime. The class loader for an array class, as returned by Class.getClassLoader() is the same as the class loader for its element type; if the element type is a primitive type, then the array class has no class loader.

(3和10,12重要)

  1. 類加載器是負責加載類的對象。類加載器是一個抽象類。給定類的二進制名,類加載器應該嘗試定位或生成構成類定義的數據(回去查找對應的class文件如果沒有解析成class文件)一個典型的策略是將名稱轉換爲文件名,然後從文件中讀取該名稱的“類文件”系統。(我們編寫的java程序類都是這樣運行)
  2. 每個類對象都包含對定義它的類加載器的引用(getClassLoader()獲取當前類的類加載器)。
  3. 數組類的類對象不是由類加載器創建的,而是由類加載器創建的根據Java運行時的要求自動執行(數組類對象是由jvm虛擬機動態創建的)。數組類的類加載器,如 getclassloader()返回的類與它的元素類型的類裝入器相同(數組元素類型是什麼類加載器就是什麼類型);如果 元素類型是基本(原生8種)類型,因此數組類沒有類裝入器。
  4. 應用程序實現的ClassLoader類加載器爲了擴展Java虛擬機動態加載的方式類
    示例
public class Test15 {

    public static void main(String[] args) {

        String[] strings = new String[2];
        System.out.println(strings.getClass().getClassLoader()); //string[]數組元素類型是String 在rt.jar包中是由根類加載器加載的

        System.out.println("----------------");

        Test15[] test15s = new Test15[2];
        System.out.println(test15s.getClass().getClassLoader());//同理 該元素是由AppClassLoader系統類加載器加載的 但是數組本身不是由類加載器加載

        System.out.println("----------------");

        int[] ints = new int[2];//如果 元素類型是基本(原生8種)類型,因此數組類沒有類裝入器。
        System.out.println(ints.getClass().getClassLoader());
        
    }
}

打印


/*
null 根類加載器
----------------
sun.misc.Launcher$AppClassLoader@18b4aac2
----------------
null 爲空
 */
  1. 安全管理器通常會使用類裝入器來指示安全域(類加載始終伴隨着安全管理器所以類加載是安全的)
  2. ClassLoader類使用委託模型進行搜索類和資源。ClassLoader的每個實例都有一個關聯的父類裝入器。當請求查找一個類或資源,一個類加載器實例將委託父類搜索類或資源,然後再嘗試查找類或資源本身。虛擬機的內置類加載器,稱爲“啓動/根類加載器”,本身沒有父類,但可以充當類裝入器實例的父類。
  3. 支持類的併發加載的類加載器稱爲 並行能力類加載器,需要註冊類的初始化時間registerAsParallelCapable。ClassLoader.registerAsParallelCapable這個方法。注意,類裝入器類被註冊爲並行類默認爲able。但是,它的子類仍然需要註冊它們自己如果他們是並行的能力。
  4. 委託模型不嚴格的環境層次結構,類加載器需要能夠並行否則類加載可能導致死鎖,因爲加載器鎖被持有類加載過程的持續時間(參見{@link #loadClass)方法
  5. 通常,Java虛擬機從本地文件加載類平臺相關的系統。例如,在UNIX系統中在CLASSPATH環境變量定義的目錄加載類
  6. 然而,有些類可能不是起源於一個文件;他們可能會產生從其他來源,如網絡,或它們可以由一個應用程序(動態代理)。方法{@link #defineClass(String, byte[], int, int)defineClass}將字節數組轉換爲類的實例可以使用以下命令創建這個新定義的類的實例 {@link Class#newInstance Class.newInstance}
  7. 類加載器創建的對象的方法和構造函數可以引用其他類。要確定所引用的類即Java虛擬機調用{@link #loadClass loadClass}方法(這個方法解決這個問題)最初創建類的類加載器
ClassLoader loade = new NetworkClassLoader(host,port);
Object main = loader.loadClass("Main", true).newInstance();
  1. 自定義加載器子類必須定義兩個方法{@link#findClass findClass}和loadClassData加載類來自網絡。一旦它下載了構成類的字節,應該使用方法{@link #defineClass defineClass} to創建一個類實例。一個示例實現是:
 class NetworkClassLoader extends ClassLoader {
          String host;
          int port;
 
          public Class findClass(String name) {
              byte[] b = loadClassData(name);
              return defineClass(name, b, 0, b.length);//通過名字將Class對象返回給調用者
          }
 
          private byte[] loadClassData(String name) {
              // load the class data from the connection
             .
          }
      }

編寫自定義類加載器

自定一 此時因爲在ClassPath下 所以會調用父類AppClassLoader的系統類加載器 所以自定義的的findClass不會被執行

public class Test16 extends ClassLoader {

    private String classLoaderName;

    private final String fileExtension = ".class";

    public Test16(String classLoaderName) {
        // this(checkCreateClassLoader(), getSystemClassLoader());
        // ClassLoader中當創建新的類加載器返回的的是系統類加載器, 所以當創建新的類加載器 默認父加載器爲系統類加載器
        super();//可加可不加
        this.classLoaderName = classLoaderName;
    }

    public Test16(ClassLoader parent, String classLoaderName) {
        // this(checkCreateClassLoader(), parent);
        //ClassLoader中當創建新的類加載器自定義父加載器 如 :
        //a繼承b b繼承ClassLoader  此時a可以拿這個構造方法將b作爲自己的雙親 不一定都交給系統類加載器
        super(parent);
        this.classLoaderName = classLoaderName;
    }


    /**
     * 查找指定二進制名字的class 這個方法應該被子類加載器實現重寫,再檢查完對應父加載器之後該方法會被loaderClass()方法調用 ,
     * 在父類中  throw new ClassNotFoundException(name); 只是拋出來一個異常必須重寫
     * 如:
     * java.lang.String
     *
     * @param className
     * @return
     * @throws ClassNotFoundException
     */
    @Override
    protected Class<?> findClass(String className) throws ClassNotFoundException {
        //對應二進制名字對應的字節數組
        byte[] data = this.loadClassData(className);
        
        System.out.println("findClass invoked" + className);
        System.out.println("class loader name" + classLoaderName);
        
        //defineClass(類名,字節數據,起,末) 創建類實例
        return this.defineClass(className, data, 0, data.length);
    }

    //獲取文件字節數據
    private byte[] loadClassData(String name) {
        InputStream is = null;
        byte[] data = null;
        ByteArrayOutputStream baos = null;
        try {
            //傳過來的文件名加上後綴
            is = new FileInputStream(new File(name + this.fileExtension));

            baos = new ByteArrayOutputStream();

            int ch = 0;
            while (-1 != (ch = is.read())) {
                baos.write(ch);
            }
            data = baos.toByteArray();

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                is.close();
                baos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return data;
    }

    public static void main(String[] args) throws Exception {
        Test16 test16 = new Test16("test16");
        test(test16);
    }

    public static void test(ClassLoader classLoader) throws IllegalAccessException, Exception {
        //改方法會調用我們重寫之後的findClass方法
        Class<?> clasz = classLoader.loadClass("com.example.demo.com.jvm.Test1");
        Object o = clasz.newInstance();
        System.out.println(o);
    }
}

打印結果

//只輸出了
com.example.demo.com.jvm.Test1@1eb44e46

基於上例重構 新增自定義路徑將class字節碼路徑放在其他位置 此時父類加載器appClassLoader無法加載 此時就會調用自己的findClass() 需要將classpath 下的需要加載的.class刪除

public class Test16 extends ClassLoader {

    private String classLoaderName;

    //路徑
    private String path;

    private final String fileExtension = ".class";

    public Test16(String classLoaderName) {
        // this(checkCreateClassLoader(), getSystemClassLoader());
        // ClassLoader中當創建新的類加載器返回的的是系統類加載器, 所以當創建新的類加載器 默認父加載器爲系統類加載器
        super();//可加可不加
        this.classLoaderName = classLoaderName;
    }

    public void setPath(String path) {
        this.path = path;
    }

    public Test16(ClassLoader parent, String classLoaderName) {
        // this(checkCreateClassLoader(), parent);
        //ClassLoader中當創建新的類加載器自定義父加載器 如 :
        //a繼承b b繼承ClassLoader  此時a可以拿這個構造方法將b作爲自己的雙親 不一定都交給系統類加載器
        super(parent);
        this.classLoaderName = classLoaderName;
    }


    /**
     * 查找指定二進制名字的class 這個方法應該被子類加載器實現重新,再檢查完對應父加載器之後該方法會被loaderClass()方法調用 ,
     * 在父類中  throw new ClassNotFoundException(name); 只是拋出來一個異常必須重寫
     * 如:
     * java.lang.String
     *
     * @param className
     * @return
     * @throws ClassNotFoundException
     */
    @Override
    protected Class<?> findClass(String className) throws ClassNotFoundException {
        //對應二進制名字對應的字節數組
        byte[] data = this.loadClassData(className);
        System.out.println("findClass invoked" + className);
        System.out.println("class loader name= " + classLoaderName);

        //defineClass(類名,字節數據,起,末) 創建類實例
        return this.defineClass(className, data, 0, data.length);
    }

    //獲取文件字節數據
    private byte[] loadClassData(String className) {
        InputStream is = null;
        byte[] data = null;
        ByteArrayOutputStream baos = null;

        className = className.replace(".", "\\");
        try {
            //傳過來的文件名加上後綴
            is = new FileInputStream(new File(this.path + className + this.fileExtension));

            baos = new ByteArrayOutputStream();

            int ch = 0;
            while (-1 != (ch = is.read())) {
                baos.write(ch);
            }
            data = baos.toByteArray();

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                is.close();
                baos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return data;
    }

    public static void main(String[] args) throws Exception {
        Test16 test16 = new Test16("test16");

//        test16.setPath("D:\\workspaces\\zookeeper\\target\\classes\\");

        test16.setPath("E:\\cx\\");

        //改方法會調用我們重寫之後的findClass方法
        Class<?> clasz = test16.loadClass("com.example.demo.com.jvm.Test1");
        System.out.println("class: " + clasz.hashCode());

        Object o = clasz.newInstance();//對象內存地址有哈希值
        System.out.println(o);
    }
}

此時findClass中的打印語句執行了 
findClass invokedcom.example.demo.com.jvm.Test1
class loader name= test16

class: 1365202186
com.example.demo.com.jvm.Test1@626b2d4a

當我們在編寫完自定義類加載器時重寫了loadClassData和findClass方法。在main方法中實例化Test16對象調用返回系統類加載器的構造函數,因爲在classpath路徑以上(雙親委託下並沒有找到對應的.class文件 所以自定義加載器去加載 此時調用classLoader的loadClass方法獲取對應的Class實例 此時自定義類加載器並沒有直接調用findClass方法 而是在loadClass方法中ClassLoader幫我們直接調用了我們自己重寫好的findclass方法
在這裏插入圖片描述因爲ClassLoader中的findClass方法只是拋出了一個異常所有我們在自定義類加載器時必須重寫對應方法
在這裏插入圖片描述當我們調用對應的方法完畢
在這裏插入圖片描述
重寫loadClassData方法將獲取對應二進制類名文件字節數組
在這裏插入圖片描述
在通過
在這裏插入圖片描述
方法獲取對應二進制名稱的Class對象
而在ClassLoader中的defineClass方法調用了重載的defineClass方法多加了個ProtectionDomain
ProtectionDomain 類封裝域的特徵,域中包裝一個類集合,在代表給定的主體集合執行這些類的實例時會授予它們一個權限集合。主要是支持支持動態安全策略
在這裏插入圖片描述
在這個方法裏面纔是真正獲取對應二進制名字的Class對象

   protected final Class<?> defineClass(String name, byte[] b, int off, int len,
                                         ProtectionDomain protectionDomain)
        throws ClassFormatError
    {
        //前置處理
        protectionDomain = preDefineClass(name, protectionDomain);
        String source = defineClassSourceLocation(protectionDomain);
        //此時調用底層本地C++代碼獲取Class 
        Class<?> c = defineClass1(name, b, off, len, protectionDomain, source);
        //後置處理拼接對象後綴名
        postDefineClass(c, protectionDomain);
        return c;
    }

自此程序運行結束 返回Class對象

ClassLoader 中loadClass 詳解

classLoader.lordClass和forName的區別(主動加載和被動加載的區別)

class.forName()除了將類的.class文件加載到jvm中之外,還會對類進行解釋,執行類中的static塊。
classLoader.lordClass只幹一件事情,就是將.class文件加載到jvm中,不會執行static中的內容,只有在newInstance纔會去執行static塊。(不初始)
Class.forName(name, initialize, loader)帶參函數也可控制是否加載static塊。並且只有調用了newInstance()方法採用調用構造函數,來創建類的對象(初始)

ClassLoader 中loadClass 此時獲取的Class還沒有鏈接 只是剛加載到JVM中

加載指定的二進制名的類此方法的實現會默認按照以下的順序尋找類

  1. 調用{@link #findLoadedClass(String)}檢查類是否已經加載(一個類只能被加載一次)
  2. 調用父類的{@link #loadClass(String) loadClass}方法,如果父類是null 就會使用虛擬機內置的根類加載器
  3. 調用{@link #findClass(String)}方法查找

如果類被發現使用上述步驟,和解析標誌爲真,此方法將調用{@link#resolveClass(Class)}方法的結果類對象。
子類ClassLoader被鼓勵重寫{@link#findClass(String)},而不是這個方法。

在整個類裝入過程中除非被覆蓋,否則此方法對的結果進行同步{@link #getClassLoadingLock getClassLoadingLock}方法

   protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // 檢查類是否已經加載(一個類只能被加載一次)
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                    if (parent != null) {
                       //如果父類不是null 就會使用虛擬機內置的根類加載器去加載二進制名(name對應的數據),
                       //子類ClassLoader被鼓勵重寫
                        c = parent.loadClass(name, false);
                    } else {
                        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();
                }
            }
           // 如果類被發現使用上述步驟,和解析標誌爲真,此方法將調用{@link#resolveClass(Class)}方法的結果類對象。
            if (resolve) {
                resolveClass(c);
            }
            //返回Class
            return c;
        }
    }

基於上例Test16繼續重構

public class Test16 extends ClassLoader {

    private String classLoaderName;

    //路徑
    private String path;

    private final String fileExtension = ".class";

    public Test16(String classLoaderName) {
        // this(checkCreateClassLoader(), getSystemClassLoader());
        // ClassLoader中當創建新的類加載器返回的的是系統類加載器, 所以當創建新的類加載器 默認父加載器爲系統類加載器
        super();//可加可不加
        this.classLoaderName = classLoaderName;
    }

    public void setPath(String path) {
        this.path = path;
    }

    public Test16(ClassLoader parent, String classLoaderName) {
        // this(checkCreateClassLoader(), parent);
        //ClassLoader中當創建新的類加載器自定義父加載器 如 :
        //a繼承b b繼承ClassLoader  此時a可以拿這個構造方法將b作爲自己的雙親 不一定都交給系統類加載器
        super(parent);
        this.classLoaderName = classLoaderName;
    }


    /**
     * 查找指定二進制名字的class 這個方法應該被子類加載器實現重新,再檢查完對應父加載器之後該方法會被loaderClass()方法調用 ,
     * 在父類中  throw new ClassNotFoundException(name); 只是拋出來一個異常必須重寫
     * 如:
     * java.lang.String
     *
     * @param className
     * @return
     * @throws ClassNotFoundException
     */
    @Override
    protected Class<?> findClass(String className) throws ClassNotFoundException {
        //對應二進制名字對應的字節數組
        byte[] data = this.loadClassData(className);
        System.out.println("findClass invoked:" + className);
        System.out.println("class loader name: " + classLoaderName);

        //defineClass(類名,字節數據,起,末) 創建類實例
        return this.defineClass(className, data, 0, data.length);
    }

    //獲取文件字節數據
    private byte[] loadClassData(String className) {
        InputStream is = null;
        byte[] data = null;
        ByteArrayOutputStream baos = null;

        className = className.replace(".", "\\");
        try {
            //傳過來的文件名加上後綴
            is = new FileInputStream(new File(this.path + className + this.fileExtension));

            baos = new ByteArrayOutputStream();

            int ch = 0;
            while (-1 != (ch = is.read())) {
                baos.write(ch);
            }
            data = baos.toByteArray();

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                is.close();
                baos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return data;
    }

    public static void main(String[] args) throws Exception {
        Test16 test16 = new Test16("test16");

//        test16.setPath("D:\\workspaces\\zookeeper\\target\\classes\\");

        test16.setPath("E:\\cx\\");

        //改方法會調用我們重寫之後的findClass方法
        Class<?> clasz = test16.loadClass("com.example.demo.com.jvm.Test1");
        System.out.println("class: " + clasz.hashCode());

        Object o = clasz.newInstance();//對象內存地址有哈希值
        System.out.println(o);

        Test16 test162 = new Test16("test17");
        Class<?> clasz2 = test16.loadClass("com.example.demo.com.jvm.Test1");
        System.out.println("class: " + clasz2.hashCode());
        Object o2 = clasz2.newInstance();//對象內存地址有哈希值
        System.out.println(o2);
    }
}

/*
當classPath下有對應的加載的.class時 第二次交給父類加載器發現已經加載所以字節拿過來用 所以此時獲取的Class類時一致的
class: 515132998
com.example.demo.com.jvm.Test1@6504e3b2
class: 515132998
com.example.demo.com.jvm.Test1@515f550a

當classPath下沒有對應的加載的.class 制定了對應的路徑 此時類獲取幾次就會加載幾次 涉及到了命名空間的問題
findClass invoked:com.example.demo.com.jvm.Test1
class loader name: test16
class: 1365202186
com.example.demo.com.jvm.Test1@626b2d4a
--------------兩個不同的命名空間------------------
findClass invoked:com.example.demo.com.jvm.Test1
class loader name: test17
class: 932583850
com.example.demo.com.jvm.Test1@cac736f

 */

總結:同一個命名空間不會出現兩個完全相同的類,不同的命名空間會出現兩個完全相同的類,父加載器加載的類不可以看到子類加載器加載的類,但是子類加載器加載的類可以看到父類加載器加載的類
在這裏插入圖片描述上例繼續改造://將loader1作爲loader2的父類加載器

public class Test16 extends ClassLoader {

    private String classLoaderName;

    //路徑
    private String path;

    private final String fileExtension = ".class";

    public Test16(String classLoaderName) {
        // this(checkCreateClassLoader(), getSystemClassLoader());
        // ClassLoader中當創建新的類加載器返回的的是系統類加載器, 所以當創建新的類加載器 默認父加載器爲系統類加載器
        super();//可加可不加
        this.classLoaderName = classLoaderName;
    }

    public void setPath(String path) {
        this.path = path;
    }

    public Test16(ClassLoader parent, String classLoaderName) {
        // this(checkCreateClassLoader(), parent);
        //ClassLoader中當創建新的類加載器自定義父加載器 如 :
        //a繼承b b繼承ClassLoader  此時a可以拿這個構造方法將b作爲自己的雙親 不一定都交給系統類加載器
        super(parent);
        this.classLoaderName = classLoaderName;
    }


    /**
     * 查找指定二進制名字的class 這個方法應該被子類加載器實現重新,再檢查完對應父加載器之後該方法會被loaderClass()方法調用 ,
     * 在父類中  throw new ClassNotFoundException(name); 只是拋出來一個異常必須重寫
     * 如:
     * java.lang.String
     *
     * @param className
     * @return
     * @throws ClassNotFoundException
     */
    @Override
    protected Class<?> findClass(String className) throws ClassNotFoundException {
        //對應二進制名字對應的字節數組
        byte[] data = this.loadClassData(className);
        System.out.println("findClass invoked:" + className);
        System.out.println("class loader name: " + classLoaderName);

        //defineClass(類名,字節數據,起,末) 創建類實例
        return this.defineClass(className, data, 0, data.length);
    }

    //獲取文件字節數據
    private byte[] loadClassData(String className) {
        InputStream is = null;
        byte[] data = null;
        ByteArrayOutputStream baos = null;

        className = className.replace(".", "\\");
        try {
            //傳過來的文件名加上後綴
            is = new FileInputStream(new File(this.path + className + this.fileExtension));

            baos = new ByteArrayOutputStream();

            int ch = 0;
            while (-1 != (ch = is.read())) {
                baos.write(ch);
            }
            data = baos.toByteArray();

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                is.close();
                baos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return data;
    }

    public static void main(String[] args) throws Exception {
        Test16 loader1 = new Test16("loader1");

//        test16.setPath("D:\\workspaces\\zookeeper\\target\\classes\\");

        loader1.setPath("E:\\cx\\");

        //改方法會調用我們重寫之後的findClass方法
        Class<?> clasz = loader1.loadClass("com.example.demo.com.jvm.Test1");
        System.out.println("class: " + clasz.hashCode());

        Object o = clasz.newInstance();//對象內存地址有哈希值
        System.out.println(o);

//        System.out.println("------------兩個不同的命名空間--------------------");
        Test16 loader2 = new Test16(loader1,"loader2");//將loader1作爲loader2的父類加載器

        loader2.setPath("E:\\cx\\");
        Class<?> clasz2 = loader2.loadClass("com.example.demo.com.jvm.Test1");
        System.out.println("class: " + clasz2.hashCode());
        Object o2 = clasz2.newInstance();//對象內存地址有哈希值
        System.out.println(o2);
    }
}

-------------------------------------------
當classPath下沒有對應的加載的.class時
Test16 loader2 = new Test16(loader1,"loader2");//將loader1作爲loader2的父類加載器

findClass invoked:com.example.demo.com.jvm.Test1
class loader name: loader1
class: 1365202186
com.example.demo.com.jvm.Test1@626b2d4a  //此時父加載器loader1已經加載完畢 loader2直接拿來使用

class: 1365202186
com.example.demo.com.jvm.Test1@5e91993f

通過上例繼續改造: 新增一個類加載器 父類設置爲loader2


public class Test16 extends ClassLoader {

    private String classLoaderName;

    //路徑
    private String path;

    private final String fileExtension = ".class";

    public Test16(String classLoaderName) {
        // this(checkCreateClassLoader(), getSystemClassLoader());
        // ClassLoader中當創建新的類加載器返回的的是系統類加載器, 所以當創建新的類加載器 默認父加載器爲系統類加載器
        super();//可加可不加
        this.classLoaderName = classLoaderName;
    }

    public void setPath(String path) {
        this.path = path;
    }

    public Test16(ClassLoader parent, String classLoaderName) {
        // this(checkCreateClassLoader(), parent);
        //ClassLoader中當創建新的類加載器自定義父加載器 如 :
        //a繼承b b繼承ClassLoader  此時a可以拿這個構造方法將b作爲自己的雙親 不一定都交給系統類加載器
        super(parent);
        this.classLoaderName = classLoaderName;
    }


    /**
     * 查找指定二進制名字的class 這個方法應該被子類加載器實現重新,再檢查完對應父加載器之後該方法會被loaderClass()方法調用 ,
     * 在父類中  throw new ClassNotFoundException(name); 只是拋出來一個異常必須重寫
     * 如:
     * java.lang.String
     *
     * @param className
     * @return
     * @throws ClassNotFoundException
     */
    @Override
    protected Class<?> findClass(String className) throws ClassNotFoundException {
        //對應二進制名字對應的字節數組
        byte[] data = this.loadClassData(className);
        System.out.println("findClass invoked:" + className);
        System.out.println("class loader name: " + classLoaderName);

        //defineClass(類名,字節數據,起,末) 創建類實例
        return this.defineClass(className, data, 0, data.length);
    }

    //獲取文件字節數據
    private byte[] loadClassData(String className) {
        InputStream is = null;
        byte[] data = null;
        ByteArrayOutputStream baos = null;

        className = className.replace(".", "\\");
        try {
            //傳過來的文件名加上後綴
            is = new FileInputStream(new File(this.path + className + this.fileExtension));

            baos = new ByteArrayOutputStream();

            int ch = 0;
            while (-1 != (ch = is.read())) {
                baos.write(ch);
            }
            data = baos.toByteArray();

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                is.close();
                baos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return data;
    }

    public static void main(String[] args) throws Exception {
        Test16 loader1 = new Test16("loader1");

//        test16.setPath("D:\\workspaces\\zookeeper\\target\\classes\\");

        loader1.setPath("E:\\cx\\");

        //改方法會調用我們重寫之後的findClass方法
        Class<?> clasz = loader1.loadClass("com.example.demo.com.jvm.Test1");
        System.out.println("class: " + clasz.hashCode());

        Object o = clasz.newInstance();//對象內存地址有哈希值
        System.out.println(o);

        System.out.println();

//        System.out.println("------------兩個不同的命名空間--------------------");
        Test16 loader2 = new Test16(loader1, "loader2");//將loader1作爲loader2的父類加載器

        loader2.setPath("E:\\cx\\");
        Class<?> clasz2 = loader2.loadClass("com.example.demo.com.jvm.Test1");
        System.out.println("class: " + clasz2.hashCode());
        Object o2 = clasz2.newInstance();//對象內存地址有哈希值
        System.out.println(o2);

        System.out.println();

        Test16 loader3 = new Test16(loader2,"loader3");
        loader3.setPath("E:\\cx\\");

        //改方法會調用我們重寫之後的findClass方法
        Class<?> clasz3 = loader3.loadClass("com.example.demo.com.jvm.Test1");
        System.out.println("class: " + clasz3.hashCode());

        Object o3 = clasz3.newInstance();//對象內存地址有哈希值
        System.out.println(o3);
    }
}
命名空間一致
findClass invoked:com.example.demo.com.jvm.Test1
class loader name: loader1
class: 1365202186
com.example.demo.com.jvm.Test1@626b2d4a loader1 先去加類加載

class: 1365202186
com.example.demo.com.jvm.Test1@5e91993f loader2 交給父類父類交給appClassLoader加載發現已經加載直接拿來用

class: 1365202186
com.example.demo.com.jvm.Test1@1c4af82c loader3 同上

類的卸載

在這裏插入圖片描述在這裏插入圖片描述在這裏插入圖片描述實例 展示自定義類被卸載

其他不變
 public static void main(String[] args) throws Exception {
        Test16 loader1 = new Test16("loader1");

        loader1.setPath("E:\\cx\\");

        //改方法會調用我們重寫之後的findClass方法
        Class<?> clasz = loader1.loadClass("com.example.demo.com.jvm.Test1");
        System.out.println("class: " + clasz.hashCode());

        Object o = clasz.newInstance();//對象內存地址有哈希值
        System.out.println(o);

        System.out.println();

        loader1 = null;

        clasz = null;

        o = null;

        System.gc();

        loader1 = new Test16("loader1");

        loader1.setPath("E:\\cx\\");

        //改方法會調用我們重寫之後的findClass方法
        clasz = loader1.loadClass("com.example.demo.com.jvm.Test1");
        System.out.println("class: " + clasz.hashCode());

        o = clasz.newInstance();//對象內存地址有哈希值
        System.out.println(o);

        System.out.println();
findClass invoked:com.example.demo.com.jvm.Test1
class loader name: loader1
class: 1365202186
com.example.demo.com.jvm.Test1@626b2d4a

findClass invoked:com.example.demo.com.jvm.Test1  //此時類沒有被引用已經被強制回收
class loader name: loader1
class: 932583850
com.example.demo.com.jvm.Test1@cac736f
findClass invoked:com.example.demo.com.jvm.Test1
class loader name: loader1
class: 1365202186
com.example.demo.com.jvm.Test1@626b2d4a
測試垃圾回收
findClass invoked:com.example.demo.com.jvm.Test1
class loader name: loader1
class: 932583850
com.example.demo.com.jvm.Test1@cac736f

在cmd窗口輸入jvisualvm 打開可視化工具
在實例中加入Thread.sleep(1000000) 也可以觀察卸載情況
在這裏插入圖片描述
在這裏插入圖片描述

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