深刻理解JVM之ClassLoader

1.什麼是類加載器

在編寫Java程序時須要使用javac命令將.java後綴名的文件編譯成.class文件,而後JVM經過執行.class文件來運行咱們寫的程序,那麼JVM怎麼才能執行.class文件呢?
這就須要類加載器了。java

2.有哪幾種類加載器

  • BootstrapClassLoader:這個類加載器能夠被稱爲引導類加載器,它由C++語言編寫,在JDK中看到的是使用native標註的方法。它負責加載jre/lib下的核心包,例如:rt.jar。
  • ExtClassLoader:它被稱爲擴展類加載器,它是由Java語言編寫,用於加載jre/lib/ext目錄中的包。
  • AppClassLoader:這個類加載器叫作應用程序類加載器,它是咱們平時使用最頻繁的一種類加載器,用於加載classpath下的.class文件,也就是咱們本身寫的程序。
  • 自定義的類加載器:該類加載器用於加載咱們指定的一些類。

3.ClassLoader之間的關係

3.1 類加載器建立過程

在jdk中找到sun.misc.Launcher文件,這個文件是JVM中的啓動器實例,在該類內部有getLauncher()方法app

public static Launcher getLauncher() {
    return launcher;
}

它返回的是launcher 實例ide

private static Launcher launcher = new Launcher();

在構造方法調用時會初始化 ExtClassLoader和AppClassLoader(BootstrapClassLoader會在Java虛擬機建立以後進行加載,因此是在此以前)。測試

public Launcher() {
    Launcher.ExtClassLoader var1;
    try {
        var1 = Launcher.ExtClassLoader.getExtClassLoader();
    } catch (IOException var10) {
        throw new InternalError("Could not create extension class loader", var10);
    }

    try {
        this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
    } catch (IOException var9) {
        throw new InternalError("Could not create application class loader", var9);
    }

    Thread.currentThread().setContextClassLoader(this.loader);
    // 非核心的就去掉了

}

經過點擊 getXxxClassLoader() 跟到最後能夠看到this

private ClassLoader(Void unused, ClassLoader parent) {
    // 經過這行可讓加載器之間創建父子級關係
    this.parent = parent;
    if (ParallelLoaders.isRegistered(this.getClass())) {
        parallelLockMap = new ConcurrentHashMap<>();
        package2certs = new ConcurrentHashMap<>();
        assertionLock = new Object();
    } else {
        // no finer-grained lock; lock on the classloader instance
        parallelLockMap = null;
        package2certs = new Hashtable<>();
        assertionLock = this;
    }
}

注意: 咱們查看ExtClassLoader時會發現這裏的parent是null,而查看AppClassLoader時傳入的parent是ExtClassLoader的對象實例。因此這裏也就印證了上面畫的圖。spa

3.2 探究加載類的流程

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) {
                    // 若是父加載器存在則使用父加載器進行加載
                    c = parent.loadClass(name, false);
                } else {
                    // 若是父加載器不存在就是用BootstrapClassLoader加載
                    c = findBootstrapClassOrNull(name);
                }
            } catch (ClassNotFoundException e) {
                // ClassNotFoundException thrown if class not found
                // from the non-null parent class loader
            }
            
            // 若是父加載器和BootstrapClassLoader都沒有加載到的話
            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);
        }
        // 返回已加載到的類或null
        return c;
    }
}

加載步驟:3d

  • 判斷當前類加載器是否已經加載目標類,若是已經加載則返回,不然使用判斷是否存在父加載器
  • 若是父加載器存在重複步驟1,直到parent == null,這時會使用 BootstrapClassLoader 加載
  • 若是父加載器和引導類加載器都沒有加載到目標類的話,就使用本身的findClass() 方法來加載目標類。

找到某個ClassLoader,這裏以AppClassLoader爲例code

static class AppClassLoader extends URLClassLoader {

在AppClassLoader中搜索findClass()方法是找不到的,因此找到每一個加載器共同的父類URLClassLoader,在這裏能夠看到findClass()方法。對象

protected Class<?> findClass(final String name)
        throws ClassNotFoundException
{
    final Class<?> result;
    try {
        result = AccessController.doPrivileged(
            new PrivilegedExceptionAction<Class<?>>() {
                public Class<?> run() throws ClassNotFoundException {
                    String path = name.replace('.', '/').concat(".class");
                    Resource res = ucp.getResource(path, false);
                    if (res != null) {
                        try {
                            return defineClass(name, res);
                        } catch (IOException e) {
                            throw new ClassNotFoundException(name, e);
                        }
                    } else {
                        return null;
                    }
                }
            }, acc);
    } catch (java.security.PrivilegedActionException pae) {
        throw (ClassNotFoundException) pae.getException();
    }
    if (result == null) {
        throw new ClassNotFoundException(name);
    }
    return result;
}

findClass方法的做用是從指定的路徑找到傳入的文件名稱的.class文件,並返回回去,該方法的主要邏輯在run()方法中。blog

4.實現本身的ClassLoader

從前面能夠知道,要想實現classLoader只須要繼承ClassLoader類並重寫 findClassloadClass 方法便可。

4.1 建立java文件

在桌面建立了一個名爲Test的Java文件,並使用javac命令進行編譯。

public class Test {

    public void sayHi() {
        System.out.println("hi!");
    }
    
    public static void main(String[] args) {
        System.out.println("Hello World!");
    }
}

4.2 完成方法重寫與測試

/**
 * Created by luyi on 2021/2/16.
 * 描述:自定義ClassLoader
 */
public class MyClassLoader extends ClassLoader {

    public String classPath;

    public MyClassLoader(String classPath) {
        this.classPath = classPath;
    }

    /**
     * 經過輸入流讀取文件
     *
     * @param name 文件全限定類名
     */
    public byte[] loadByte(String name) throws IOException {

        if (name == null || name.length() == 0) {
            return new byte[1024];
        }

        name = name.replaceAll("\\.", "/");
        FileInputStream fis = new FileInputStream(classPath + "/" + name + ".class");
        int len = fis.available();
        byte[] data = new byte[len];
        fis.read(data);
        fis.close();
        return data;
    }

    /**
     * 將指定的路徑中的指定的文件進行加載
     *
     * @param name 全限定類名
     * @return
     * @throws ClassNotFoundException
     */
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {

        if (name == null || name.length() == 0) {
            return null;
        }

        try {
            byte[] data = loadByte(name);
            return defineClass(name, data, 0, data.length);
        } catch (IOException e) {
            e.printStackTrace();
            throw new ClassNotFoundException();
        }
    }

    public static void main(String[] args) throws Exception {

        MyClassLoader loader = new MyClassLoader("/Users/luyi/Desktop");
        Class<?> clazz = loader.loadClass("Test", false);
        Object instance = clazz.newInstance();
        Method sayHi = clazz.getDeclaredMethod("sayHi", null);
        sayHi.invoke(instance, null);

        System.out.println(clazz.getClassLoader().getClass().getName());
    }
}

運行結果:

從運行結果能夠看出,自定義的類加載器已經完成了對指定文件的加載,並正確的執行了方法。

相關文章
相關標籤/搜索