從頭開始學(xué)習(xí)->JVM(五):類加載器(下)【源碼分析】

前言

上一篇文章,《從頭開始學(xué)習(xí)JVM(四):類加載器(中)》,我們知道了類加載器的基本原理,但是知道了這些原理之后,我們對類加載器的底層的邏輯,算不上有多清楚明白。

我們僅僅是意識到,類加載器的種類有多少,類加載器的加載機制雙親委派模型以及如何打破雙親委派模型等等等相關(guān)的一些原理。

但是在這些原理的背后,是什么在支撐著呢?

想一想吧,所謂的JVM,本身就是一直概念上的產(chǎn)物,并不是物理上的東西,那么作為JVM一部分的類加載器,以及我們的類加載流程,也只是我們在概念上的劃分。而支持著這些概念上劃分的,就是我們的代碼。這些代碼里面,有C++代碼,有java代碼,這些代碼,組合成了一個個閉合的邏輯,在現(xiàn)實層面,實現(xiàn)了我們所要實現(xiàn)的原理和概念理論。

今天這篇文章,我們嘗試從代碼層面,來解讀我們的類加載器以及一些相關(guān)的類加載機制。

正文

1.Launcher類

由于BootStapClassLoader加載器是由C++語言寫的,是嵌入到了JVM內(nèi)核中,也就是說,從java代碼的層面我們是看不到的,因此我們不去細究這個啟動類加載器。

JVM生成的第一個類是Launcher類,先看代碼,如下:

    public static void main(String[] args) {
        ClassLoader classLoader = Launcher.class.getClassLoader();
        System.out.println("加載器:" + classLoader);
    }

代碼運行結(jié)果為:

E:\jdk\bin\java.exe

加載器:null

Process finished with exit code 0

請注意,最后的根加載器打印出的是null,這不代表著就是真的null,只是因為啟動類加載器是由C++寫的,所以在java中顯示就是為null。

通過代碼運行的結(jié)果為null,我們可以得知,這個類是由BootstrapClassLoader加載器加載的。

也就是說,JVM在啟動的時候,就生成了BootstrapClassLoader加載器,而BootrapClassLoader加載器會JVM啟動的第一時間就會去創(chuàng)建Launcher類的實例。

那么,為什么要創(chuàng)建Laucher類的實例呢?我們先來看一下Launcher類的內(nèi)部代碼,如下:

public class Launcher {
    private static URLStreamHandlerFactory factory = new Launcher.Factory();
    private static Launcher launcher = new Launcher();
    private static String bootClassPath = System.getProperty("sun.boot.class.path");
    private ClassLoader loader;
    private static URLStreamHandler fileHandler;

    public static Launcher getLauncher() {
        return launcher;
    }

    public Launcher() {
        Launcher.ExtClassLoader var1;
        try {
           //創(chuàng)建ExtClassLoader加載器
            var1 = Launcher.ExtClassLoader.getExtClassLoader();
        } catch (IOException var10) {
            throw new InternalError("Could not create extension class loader", var10);
        }

        try {
            //通過創(chuàng)建的ExtClassLoader加載器來獲取AppClassLoader設(shè)置為默認加載器加載器。
            //并且將AppClassLoader設(shè)置為默認加載器
            this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
        } catch (IOException var9) {
            throw new InternalError("Could not create application class loader", var9);
        }

        Thread.currentThread().setContextClassLoader(this.loader);
        String var2 = System.getProperty("java.security.manager");
        if (var2 != null) {
            SecurityManager var3 = null;
            if (!"".equals(var2) && !"default".equals(var2)) {
                try {
                    var3 = (SecurityManager)this.loader.loadClass(var2).newInstance();
                } catch (IllegalAccessException var5) {
                    ;
                } catch (InstantiationException var6) {
                    ;
                } catch (ClassNotFoundException var7) {
                    ;
                } catch (ClassCastException var8) {
                    ;
                }
            } else {
                var3 = new SecurityManager();
            }

            if (var3 == null) {
                throw new InternalError("Could not create SecurityManager: " + var2);
            }

            System.setSecurityManager(var3);
        }

    }
}

通過這段代碼,我們發(fā)現(xiàn),Launcher類中有一個構(gòu)造方法Launcher(),這個方法在Launcher創(chuàng)建的時候,就會創(chuàng)建ExtClassLoader加載器,通過創(chuàng)建的ExtClassLoader加載器來創(chuàng)建AppClassLoader加載器,并且將AppClassLoader設(shè)置為默認的系統(tǒng)加載器(也就是當前線程的上下文類加載器)。

也因此,當我們想要獲取當前應(yīng)用程序的AppClassLoader加載器的時候,可以通過以下代碼獲?。?/p>

    public static void main(String[] args) {
        ClassLoader classLoader = Launcher.getLauncher().getClassLoader();
        System.out.println("加載器:" + classLoader);
    }

運行結(jié)果如下:

E:\jdk\bin\java.exe

加載器:sun.misc.Launcher$AppClassLoader@18b4aac2

Process finished with exit code 0

然后,既然創(chuàng)建了ExtClassLoader加載器和AppClassLoader加載器,那么我們可以去看看這兩個加載器的源碼。請注意的是,這兩個加載器都是Launcher類的靜態(tài)內(nèi)部類。

2.ExtclassLoader和AppClassLoader

ExtclassLoader:

static class ExtClassLoader extends URLClassLoader類, {
        public static Launcher.ExtClassLoader getExtClassLoader() throws IOException {
            final File[] var0 = getExtDirs();

            try {
                return (Launcher.ExtClassLoader)AccessController.doPrivileged(new PrivilegedExceptionAction<Launcher.ExtClassLoader>() {
                    public Launcher.ExtClassLoader run() throws IOException {
                        int var1 = var0.length;

                        for(int var2 = 0; var2 < var1; ++var2) {
                            MetaIndex.registerDirectory(var0[var2]);
                        }

                        return new Launcher.ExtClassLoader(var0);
                    }
                });
            } catch (PrivilegedActionException var2) {
                throw (IOException)var2.getException();
            }
        }
    }

ExtclassLoader加載器繼承了URLClassLoader類,URLClassLoader這個類是繼承了頂級類ClassLoader的,是ClassLoader類的一個擴展。

ExtclassLoader沒有重寫ClassLoader類的loadClass()方法,而是直接調(diào)用了頂級類ClassLoader的loadClass()方法。

AppClassLoader:

static class AppClassLoader extends URLClassLoader {
        final URLClassPath ucp = SharedSecrets.getJavaNetAccess().getURLClassPath(this);

        public static ClassLoader getAppClassLoader(final ClassLoader var0) throws IOException {
            final String var1 = System.getProperty("java.class.path");
            final File[] var2 = var1 == null ? new File[0] : Launcher.getClassPath(var1);
            return (ClassLoader)AccessController.doPrivileged(new PrivilegedAction<Launcher.AppClassLoader>() {
                public Launcher.AppClassLoader run() {
                    URL[] var1x = var1 == null ? new URL[0] : Launcher.pathToURLs(var2);
                    return new Launcher.AppClassLoader(var1x, var0);
                }
            });
        }

        public Class<?> loadClass(String var1, boolean var2) throws ClassNotFoundException {
            int var3 = var1.lastIndexOf(46);
            if (var3 != -1) {
                SecurityManager var4 = System.getSecurityManager();
                if (var4 != null) {
                    var4.checkPackageAccess(var1.substring(0, var3));
                }
            }

            if (this.ucp.knownToNotExist(var1)) {
                Class var5 = this.findLoadedClass(var1);
                if (var5 != null) {
                    if (var2) {
                        this.resolveClass(var5);
                    }

                    return var5;
                } else {
                    throw new ClassNotFoundException(var1);
                }
            } else {
                return super.loadClass(var1, var2);
            }
        }
    }

AppclassLoader加載器也繼承了URLClassLoader類。

AppclassLoader重寫了ClassLoader類的loadClass()方法。

3.繼承了ClassLoader的URLClassLoader類

java.net.URLClassLoader類是繼承了ClassLoader的一個類,是ClassLoader類的一個擴展。

ClassLoader只能加載classpath下面的類,而URLClassLoader則可以加載任意路徑下的類。

一般動態(tài)加載類的時候,都是直接用Class.forName()這個方法,但這個方法只能創(chuàng)建程序中已經(jīng)引用的類,并且只能用包名的方法進行索引,比如Java.lang.String,不能對一個.class文件或者一個不在程序引用里的.jar包中的類進行創(chuàng)建。

URLClassLoader提供了這個功能,它讓我們可以通過以下幾種方式進行加載:

  1. 文件: (從文件系統(tǒng)目錄加載)
  2. jar包: (從Jar包進行加載)
  3. Http: (從遠程的Http服務(wù)進行加載)

4.抽象的類加載器ClassLoader(頂級類)

java.lang.ClassLoader是Java層面對類加載器的抽象,這個類規(guī)范了類加載的基本流程,是類加載中的頂級類,其中比較重要的屬性及方法如下:

  1. parent():父類加載器的引用,一個類加載器通常都會保存一個父類加載器的引用,用于實現(xiàn)雙親委派機制。
  2. loadClass()方法,該方法為類加載器的核心方法,其中實現(xiàn)了雙親委派的邏輯。
    代碼如下:
public abstract class ClassLoader {
    //通過這個類,我們可以找到這個加載器的父類加載器
    private final ClassLoader parent;

    //name是要加載的類的名稱,resolve是判斷這個類是否要解析
    protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // 首先,檢查類是否已經(jīng)加載
            Class<?> c = findLoadedClass(name);
            
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                    if (parent != null) {
            //如果父類加載器不為null(也就是不是啟動類加載器),那就調(diào)用父類加載器的loadClass()方法來加載這個類
                        c = parent.loadClass(name, false);
                    } else {
                    //如果父類加載器為null,那么就獲取到啟動類加載器BootstrapClassLoader來加載這個類
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // 如果沒有找到類,則拋出ClassNotFoundException異常
                    // from the non-null parent class loader
                }

                if (c == null) {
                    long t1 = System.nanoTime();
                    //如果仍然沒有找到,那么就調(diào)用這個加載器本身的findClass()去找到這個類
                    c = findClass(name);

                    // 這是定義類加載器,記錄數(shù)據(jù)
                    sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    sun.misc.PerfCounter.getFindClasses().increment();
                }
            }
            if (resolve) {
                //如果傳進來的參數(shù)resolve為true,那么我們就去解析這個類
                resolveClass(c);
            }
            //返回這個類對應(yīng)的Class對象
            return c;
        }
    }
    }

要說明一下,在這個loadClass()方法中,我們調(diào)用的方法大部分都是native方法,說白了都是使用了C++寫的,在java里面是看不到具體實現(xiàn)的。

從以上代碼我們可以看出來,是實現(xiàn)了類加載機制之雙親委派模型的。

5.加載器之間的層級關(guān)系

類加載器有著一定的層級關(guān)系,比如說userClassLoader的父類加載器是AppClassLoader,而AppClassLoader的父類加載器是ExtClassLoader,而ExtClassLoader的父類加載器是BootStrapClassLoader,而BootStrapClassLoader就是最高層級的類加載器了。

我先寫出如下一段代碼:

public static void main(String[] args) throws IOException {
        //獲取應(yīng)用程序類加載器AppClassLoader
        ClassLoader appClassLoader = ClassLoader.getSystemClassLoader();
        System.out.println("應(yīng)用程序類加載器:" + appClassLoader);
        Enumeration<URL> enums = appClassLoader.getResources("");
        while (enums.hasMoreElements()) {
            System.out.println("應(yīng)用程序類加載器加載路徑:"+enums.nextElement());
            break;
        }
        
        //獲取應(yīng)用程序類加載器的父類加載器(也就是擴展類加載器ExtClassLoader)
        ClassLoader extClassLoader = appClassLoader.getParent();
        System.out.println("擴展類加載器:" + extClassLoader);
        System.out.println("擴展類加載器加載路徑:" + System.getProperty("java.ext.dirs"));
        
        //獲取擴展類加載器的父類加載器(也就是啟動類加載器BootStrapClassLoader)
        ClassLoader bootClassLoader = extClassLoader.getParent();
        System.out.println("根類加載器:" + bootClassLoader);
    }

這段代碼,運行的結(jié)果如下:

E:\jdk\bin\java.exe 

應(yīng)用程序類加載器 :sun.misc.Launcher$AppClassLoader@18b4aac2

擴展類加載器 :sun.misc.Launcher$ExtClassLoader@156643d4

根類加載器 :null

Process finished with exit code 0

所以我們可以看到,程序最后運行的結(jié)果,是和我們代碼里面所需要的結(jié)果,是一致的。也就是說,確實如我之前所說,總結(jié)如下:

  1. userClassLoader的父類加載器是AppClassLoader。
  2. AppClassLoader的父類加載器是ExtClassLoader。
  3. ExtClassLoader的父類加載器是BootStrapClassLoader。
  4. BootStrapClassLoader是最高層級的類加載器。
  5. AppClassLoader是程序默認的類加載器。

6.類加載器的加載路徑

在上面那段代碼的基礎(chǔ)上,我們稍微修改一下代碼:

    public static void main(String[] args){
        //獲取到啟動類加載器BootStrapClassLoader
        URL[] urls = sun.misc.Launcher.getBootstrapClassPath().getURLs();
        for(URL url : urls) {
            System.out.println("BootstrapClassLoader的加載路徑: "+url);
            break;
        }
        
        //獲取到擴展類加載器ExtClassLoader
        URLClassLoader extClassLoader = (URLClassLoader)ClassLoader.getSystemClassLoader().getParent();
        urls = extClassLoader.getURLs();
        for(URL url : urls){
            System.out.println("ExtClassLoader的加載路徑: "+url);
            break;
        }
        
        //取得應(yīng)用(系統(tǒng))類加載器AppClassLoader
        ClassLoader appClassLoader = ClassLoader.getSystemClassLoader();
        Enumeration<URL> enums = appClassLoader.getResources("");
        while (enums.hasMoreElements()) {
            System.out.println("AppClassLoader的加載路徑:"+enums.nextElement());
            break;
        }
    }

得出的代碼的運行結(jié)果如下:

E:\jdk\bin\java.exe 

BootstrapClassLoader負責(zé)加載存放在 <JAVA_HOME>\lib目錄
2. 的加載路徑: file:/E:/jdk/jre/lib/resources.jar

ExtClassLoader的加載路徑: file:/E:/jdk/jre/lib/ext/access-bridge-64.jar

AppClassLoader的加載路徑: file:/L:/order-service/order-service-web/target/classes/

Process finished with exit code 0

因此,從最后的結(jié)果來看,我們可以獲取到這些類加載器的加載路徑,總結(jié)如下:

  1. BootstrapClassLoader負責(zé)加載存放在 <JAVA_HOME>\lib目錄
  2. ExtClassLoader主要加載JAVA中的一些拓展類,java.ext.dirs目錄中加載類庫,或者從JDK安裝目錄:jre/lib/ext目錄下加載類庫,是啟動類加載器的子類。
  3. AppClassLoader負責(zé)加載環(huán)境變量classpath或者系統(tǒng)屬性java.class.path指定路徑下的類庫。

7.手寫一個UserClassLoader加載器

我們知道,實現(xiàn)屬于自己的類加載器UserClassLoader有兩種方式:

  1. 繼承java.lang.ClassLoader類,重寫findClass()方法
  2. 如果沒有太復(fù)雜的需求,可以直接繼承URLClassLoader類,重寫loadClass方法,具體可參考AppClassLoader和ExtClassLoader(我在上面已經(jīng)截圖說明了,請注意,這種方案會打破雙親委派模型)。

第二種方案,直接模擬AppClassLoader或者ExtClassLoader的源碼就可以了,我們今天,就來通過第一種方案,來手寫一個我們的UserClassLoader加載器。

7.1 創(chuàng)建一個加載器MyUserClassLoader

package com.blog.permission.classLoad;

import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;

public class MyUserClassLoader extends ClassLoader{
    private String rootPath;

    public MyUserClassLoader() {
        super();
    }

    public MyUserClassLoader(String rootPath) {
        this.rootPath = rootPath;
    }

    /**
     * 用于尋找類文件
     * @param className 類文件的全限定名
     * @return
     * @throws ClassNotFoundException
     */
    @Override
    protected Class<?> findClass(String className)  {
        Class<?> clz = findLoadedClass(className);
        if (clz != null)
            return clz;
        byte[] classData = loadClassData(className);
        clz = defineClass(className, classData, 0, classData.length);
        return clz;
    }

    /**
     * 這是將文件轉(zhuǎn)換為二進制字節(jié)碼
     * @param className
     * @return
     */
    private byte[] loadClassData(String className) {
        String pathName = rootPath + className.replace(".", "/") + ".class";
        System.out.println(pathName);
        byte[] bytes = null;
        try (FileInputStream fis = new FileInputStream(pathName);
             ByteArrayOutputStream bos = new ByteArrayOutputStream();) {
            byte[] flush = new byte[1024 * 1024];
            int len = -1;
            while (-1 != (len = fis.read(flush))) {
                bos.write(flush);
            }
            bytes = bos.toByteArray();
        } catch (Exception e) {
            System.out.println("異常");
        }
        return bytes;
    }
}

7.2 創(chuàng)建一個要加載的java類:Shuaige.java

package com.blog.permission.classLoad;


public class Shuaige {
    public static void main(String[] args) {
        System.out.println("I'm so cool,you underStand?");
    }
}

7.3 將shuaige.java文件編譯成Shuaige.class文件

image

7.4 創(chuàng)建一個運行的類

package com.blog.permission.classLoad;

public class TestClassLoad {

    public static void main(String[] args) throws ClassNotFoundException {
        MyUserClassLoader fileSystemClassLoader = new MyUserClassLoader("/com/blog/permission/classLoad");

        Class<?> c = fileSystemClassLoader.loadClass("com.blog.permission.classLoad.Shuaige");

        System.out.println(c);
    }
}

最后形成的層級目錄如下所示:


image

7.5 運行結(jié)果如下

E:\jdk\bin\java.exe 

class com.blog.permission.classLoad.Shuaige

Process finished with exit code 0

總結(jié)

通過對加載器的代碼的分析和解讀,很明顯,我們對類加載的機制有了更加深刻的了解。

當然,某種程度上來講,本篇文章的源碼解析,也只是解析了一部分而已,還有很多地方是沒有涉及到的。但是透過這些源碼,我們對類加載器,有了質(zhì)感上的提升,類加載器以及類加載過程,在我們心里,不再是虛擬的不落到實處的概念,而是真真切切存在著的由代碼實現(xiàn)了的東西了。

到此,類加載器的整個解讀就已經(jīng)結(jié)束了。

參考博客

https://blog.csdn.net/how_interesting/article/details/80091472

https://blog.csdn.net/chuodan5158/article/details/100765519

https://www.cnblogs.com/chinaifae/p/10401523.html

https://blog.csdn.net/weixin_39161031/article/details/83000750

https://www.cnblogs.com/rogge7/p/7766522.html

參考書籍

周志明《深入理解java虛擬機》

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容