Java類加載器

Java類加載器
一、JVM中的ClassLoader
1、Bootstrp loader(引導類加載器)
Bootstrp加載器是用C++語言寫的,它是在Java虛擬機啓動後初始化的,它主要負責加載%JAVA_HOME%/jre/lib,-Xbootclasspath參數指定的路徑以及%JAVA_HOME%/jre/classes中的類。

2、ExtClassLoader  (擴展類加載器)
Bootstrp loader加載ExtClassLoader,並且將ExtClassLoader的父加載器設置爲Bootstrp loader.ExtClassLoader是用Java寫的,具體來說就是 sun.misc.Launcher$ExtClassLoader,ExtClassLoader主要加載%JAVA_HOME%/jre/lib/ext,此路徑下的所有classes目錄以及java.ext.dirs系統變量指定的路徑中類庫。

3、AppClassLoader (系統類加載器)
Bootstrp loader加載完ExtClassLoader後,就會加載AppClassLoader,並且將AppClassLoader的父加載器指定爲 ExtClassLoader。AppClassLoader也是用Java寫成的,它的實現類是 sun.misc.Launcher$AppClassLoader,另外我們知道ClassLoader中有個getSystemClassLoader方法,此方法返回的正是AppclassLoader.AppClassLoader主要負責加載classpath所指定的位置的類或者是jar文檔,它也是Java程序默認的類加載器。

4、CustomClassLoader(用戶自定義類加載器)

除了系統提供的類加載器以外,開發人員可以通過繼承 java.lang.ClassLoader類的方式實現自己的類加載器,以滿足一些特殊的需求。

 

以下測試代碼可以證明此層次結構:

public class testClassLoader {
public static void main(String[] args) {

    //application class loader
    System.out.println("1-"+ClassLoader.getSystemClassLoader());
    //extensions class loader
    System.out.println("2-"+ClassLoader.getSystemClassLoader().getParent());
    //bootstrap class loader
    System.out.println("3"+ClassLoader.getSystemClassLoader().getParent().getParent());
}

}
輸出結果:

可以看出ClassLoader類是由AppClassLoader加載的。他的父親是ExtClassLoader,ExtClassLoader的父親無法獲取是因爲它是用C++實現的。

 

二、雙親委派機制
  某個特定的類加載器在接到加載類的請求時,首先將加載任務委託交給父類加載器,父類加載器又將加載任務向上委託,直到最父類加載器,如果最父類加載器可以完成類加載任務,就成功返回,如果不行就向下傳遞委託任務,由其子類加載器進行加載。

雙親委派機制的好處:

  保證java核心庫的安全性(例如:如果用戶自己寫了一個java.lang.String類就會因爲雙親委派機制不能被加載,不會破壞原生的String類的加載)

代理模式

  與雙親委派機制相反,代理模式是先自己嘗試加載,如果無法加載則向上傳遞。tomcat就是代理模式。

 

三、自定義類加載器
package com.test.classloader;

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

public class MyClassLoader extends ClassLoader {

private String classpath;

public MyClassLoader(String classpath) {

    this.classpath = classpath;
}

@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
    try {
        byte[] classDate = getDate(name);

        if (classDate == null) {
        }

        else {
            // defineClass方法將字節碼轉化爲類
            return defineClass(name, classDate, 0, classDate.length);
        }

    } catch (IOException e) {

        e.printStackTrace();
    }

    return super.findClass(name);
}

// 返回類的字節碼
private byte[] getDate(String className) throws IOException {
    InputStream in = null;
    ByteArrayOutputStream out = null;
    String path = classpath + File.separatorChar + className.replace('.', File.separatorChar) + ".class";
    try {
        in = new FileInputStream(path);
        out = new ByteArrayOutputStream();
        byte[] buffer = new byte[2048];
        int len = 0;
        while ((len = in.read(buffer)) != -1) {
            out.write(buffer, 0, len);
        }
        return out.toByteArray();
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } finally {
        in.close();
        out.close();
    }
    return null;
}

}
測試自定義的類加載器

創建一個測試類Test

cmd控制檯執行javac Test.java,將生成的Test.class文件放到D盤test文件夾->com文件夾->test文件夾->classloader文件夾下。

package com.test.classloader;

public class Test {

public void say(){

    System.out.println("Hello MyClassLoader");
}

}
利用如下代碼進行測試 

package com.test.classloader;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class TestMyClassLoader {

public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, SecurityException, IllegalArgumentException, InvocationTargetException {
    // 自定義類加載器的加載路徑
    MyClassLoader myClassLoader = new MyClassLoader("d:\\test");
    // 包名+類名
    Class c = myClassLoader.loadClass("com.test.classloader.Test");

    if (c != null) {
        Object obj = c.newInstance();
        Method method = c.getMethod("say", null);
        method.invoke(obj, null);
        System.out.println(c.getClassLoader().toString());
    }
}

}
輸出結果: 

 

四、類加載過程詳解
JVM將類加載過程分爲三個步驟:裝載(Load),鏈接(Link)和初始化(Initialize)

1) 裝載:

  查找並加載類的二進制數據;

2)鏈接:

  驗證:確保被加載類信息符合JVM規範、沒有安全方面的問題。

  準備:爲類的靜態變量分配內存,並將其初始化爲默認值。

  解析:把虛擬機常量池中的符號引用轉換爲直接引用。

3)初始化:

  爲類的靜態變量賦予正確的初始值。

ps:解析部分需要說明一下,Java 中,虛擬機會爲每個加載的類維護一個常量池【不同於字符串常量池,這個常量池只是該類的字面值(例如類名、方法名)和符號引用的有序集合。 而字符串常量池,是整個JVM共享的】這些符號(如int a = 5;中的a)就是符號引用,而解析過程就是把它轉換成指向堆中的對象地址的相對地址。

 

類的初始化步驟:

1)如果這個類還沒有被加載和鏈接,那先進行加載和鏈接

2)假如這個類存在直接父類,並且這個類還沒有被初始化(注意:在一個類加載器中,類只能初始化一次),那就初始化直接的父類(不適用於接口)

3)如果類中存在static標識的塊,那就依次執行這些初始化語句。

作者:碼莎拉蒂
來源:CSDN
原文:https://blog.csdn.net/ysl19910806/article/details/90748446
版權聲明:本文爲博主原創文章,轉載請附上博文鏈接!

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