Java Review - ClassLoader

ClassLoader就是類(lèi)加載器。ClassLoader的作用就是將class文件加載到j(luò)vm虛擬機(jī)中去。jvm啟動(dòng)時(shí),并不會(huì)一次性加載所有的class文件,而是按需動(dòng)態(tài)加載。


Class文件

平時(shí)我們?cè)贗DE上編寫(xiě)的都是.java文件,.java文件并不能直接在JVM上運(yùn)行,例子:

public class Main {
    //運(yùn)行入口main函數(shù)
    public static void main(String[] args) {
        System.out.println("Hello world!");
    }
}

java文件


編譯文件


生成.class文件


運(yùn)行.class文件


.class文件是字節(jié)碼格式文件,java虛擬機(jī)不能識(shí)別.java源文件,只能識(shí)別運(yùn)行.class文件,因此我們需要用javac將.java轉(zhuǎn)換為.class文件。


JAVA類(lèi)加載流程

三大類(lèi)加載器:

  • Bootstrap ClassLoader 最頂層的加載類(lèi),主要加載 核心類(lèi)庫(kù),%JRE_HOME%\lib下的rt.jar、resources.jar、charsets.jar和class等。
  • Extention ClassLoader 擴(kuò)展的類(lèi)加載器,加載 目錄%JRE_HOME%\lib\ext目錄下的jar包和class文件。
  • Appclass Loader也稱(chēng)為SystemAppClass 加載 當(dāng)前應(yīng)用的classpath的所有類(lèi)。

PS:Bootstrap ClassLoader可以通過(guò)java -Xbootclasspath/a:path來(lái)修改加載的目錄,而Extention ClassLoader可以通過(guò)-D java.ext.dirs來(lái)修改加載的目錄


入口源碼

查看精簡(jiǎn)源碼,以下是java虛擬機(jī)的入口應(yīng)用

public class Launcher {
    private static Launcher launcher = new Launcher();
    private static String bootClassPath =
        System.getProperty("sun.boot.class.path");

    public static Launcher getLauncher() {
        return launcher;
    }

    private ClassLoader loader;

    public Launcher() {
        // Create the extension class loader
        ClassLoader extcl;
        try {
            extcl = ExtClassLoader.getExtClassLoader();
        } catch (IOException e) {
            throw new InternalError(
                "Could not create extension class loader", e);
        }

        // Now create the class loader to use to launch the application
        try {
            loader = AppClassLoader.getAppClassLoader(extcl);
        } catch (IOException e) {
            throw new InternalError(
                "Could not create application class loader", e);
        }

        //設(shè)置AppClassLoader為線程上下文類(lèi)加載器,這個(gè)文章后面部分講解
        Thread.currentThread().setContextClassLoader(loader);
    }

    /*
     * Returns the class loader used to launch the main application.
     */
    public ClassLoader getClassLoader() {
        return loader;
    }
    /*
     * The class loader used for loading installed extensions.
     */
    static class ExtClassLoader extends URLClassLoader {}

    /**
     * The class loader used for loading from java.class.path.
     * runs in a restricted security context.
     */
    static class AppClassLoader extends URLClassLoader {}
}
  1. Launcher初始化了ExtClassLoader和AppClassLoader
  2. Launcher中并沒(méi)有看見(jiàn)BootstrapClassLoader,但通過(guò)System.getProperty("sun.boot.class.path") 得到了字符串bootClassPath,這個(gè)應(yīng)該就是BootstrapClassLoader加載的jar包路徑。

為了驗(yàn)證剛才說(shuō)的第2點(diǎn),我們運(yùn)行下程序:


果然都是jre下的jar包或class文件


ExtClassLoader源碼

/*
* The class loader used for loading installed extensions.
*/
static class ExtClassLoader extends URLClassLoader {

    static {
        ClassLoader.registerAsParallelCapable();
    }

    /**
     * create an ExtClassLoader. The ExtClassLoader is created
     * within a context that limits which files it can read
     */
    public static ExtClassLoader getExtClassLoader() throws IOException
    {
        final File[] dirs = getExtDirs();

        try {
            // Prior implementations of this doPrivileged() block supplied
            // aa synthesized ACC via a call to the private method
            // ExtClassLoader.getContext().

            return AccessController.doPrivileged(
                new PrivilegedExceptionAction<ExtClassLoader>() {
                    public ExtClassLoader run() throws IOException {
                        int len = dirs.length;
                        for (int i = 0; i < len; i++) {
                            MetaIndex.registerDirectory(dirs[i]);
                        }
                        return new ExtClassLoader(dirs);
                    }
                });
        } catch (java.security.PrivilegedActionException e) {
            throw (IOException) e.getException();
        }
    }

    private static File[] getExtDirs() {
        String s = System.getProperty("java.ext.dirs");
        File[] dirs;
        if (s != null) {
            StringTokenizer st =
                new StringTokenizer(s, File.pathSeparator);
            int count = st.countTokens();
            dirs = new File[count];
            for (int i = 0; i < count; i++) {
                dirs[i] = new File(st.nextToken());
            }
        } else {
            dirs = new File[0];
        }
        return dirs;
    }

......
}

前面說(shuō)過(guò),ExtClassLoader可以通過(guò)java -Xbootclasspath/a:path來(lái)修改加載的目錄,我們打印下String s = System.getProperty("java.ext.dirs");

果然都是Extensions下的jar包或class文件


AppClassLoader源碼

/**
 * The class loader used for loading from java.class.path.
 * runs in a restricted security context.
 */
static class AppClassLoader extends URLClassLoader {


    public static ClassLoader getAppClassLoader(final ClassLoader extcl)
        throws IOException
    {
        final String s = System.getProperty("java.class.path");
        final File[] path = (s == null) ? new File[0] : getClassPath(s);

    
        return AccessController.doPrivileged(
            new PrivilegedAction<AppClassLoader>() {
                public AppClassLoader run() {
                URL[] urls =
                    (s == null) ? new URL[0] : pathToURLs(path);
                return new AppClassLoader(urls, extcl);
            }
        });
    }

    ......
}

按照前面的規(guī)矩,打印final String s = System.getProperty("java.class.path");

圖上指的路徑,就是項(xiàng)目存放編譯生成的class的路徑

此時(shí):我們已經(jīng)知道BootstrapClassLoader、ExtClassLoader、AppClassLoader實(shí)際就是查詢(xún)相應(yīng)環(huán)境屬性sun.boot.class.path、java.ext.dirsjava.class.path來(lái)加載資源文件的。

我們?cè)谂軅€(gè)例子:

Main.class這個(gè)類(lèi)的類(lèi)加載為AppClassLoader,那!為啥String.class卻報(bào)錯(cuò)了,然道是Stirng.class這個(gè)類(lèi)沒(méi)有類(lèi)加載器加載。答案是否定的,String.class不僅有類(lèi)加載器加載,且是Bootstrap ClassLoader加載的。


每個(gè)類(lèi)加載器都有一個(gè)父加載器

比如Main.class的父加載器是AppClassLoader,那AppClassLoader的父加載器呢?可以使用getParent

很明顯看出AppClassLoader的父加載器就是ExtClassLoader,那ExtClassLoader的父加載器呢?

又是空指針,然道ExtClassLoader沒(méi)有父加載器?


父加載器不是父類(lèi)

static class ExtClassLoader extends URLClassLoader {}
static class AppClassLoader extends URLClassLoader {}

ExtClassLoader和AppClassLoader其實(shí)都是URLClassLoader的子類(lèi),為啥AppClassLoader的getParent()得到的卻是ExtClassLoader實(shí)例呢?我們先來(lái)看看URLClassLoader。

URLClassLoader的源碼中并沒(méi)有找到getParent()方法。這個(gè)方法在ClassLoader.java中。

ClassLoader源碼

public abstract class ClassLoader {

    // The parent class loader for delegation
    // Note: VM hardcoded the offset of this field, thus all new fields
    // must be added *after* it.
    private final ClassLoader parent;
    // The class loader for the system
        // @GuardedBy("ClassLoader.class")
    private static ClassLoader scl;

    private ClassLoader(Void unused, ClassLoader parent) {
        this.parent = parent;
        ...
    }
    protected ClassLoader(ClassLoader parent) {
        this(checkCreateClassLoader(), parent);
    }
    protected ClassLoader() {
        this(checkCreateClassLoader(), getSystemClassLoader());
    }
    public final ClassLoader getParent() {
        if (parent == null)
            return null;
        return parent;
    }
    public static ClassLoader getSystemClassLoader() {
        initSystemClassLoader();
        if (scl == null) {
            return null;
        }
        return scl;
    }

    private static synchronized void initSystemClassLoader() {
        if (!sclSet) {
            if (scl != null)
                throw new IllegalStateException("recursive invocation");
            sun.misc.Launcher l = sun.misc.Launcher.getLauncher();
            if (l != null) {
                Throwable oops = null;
                //通過(guò)Launcher獲取ClassLoader
                scl = l.getClassLoader();
                try {
                    scl = AccessController.doPrivileged(
                        new SystemClassLoaderAction(scl));
                } catch (PrivilegedActionException pae) {
                    oops = pae.getCause();
                    if (oops instanceof InvocationTargetException) {
                        oops = oops.getCause();
                    }
                }
                if (oops != null) {
                    if (oops instanceof Error) {
                        throw (Error) oops;
                    } else {
                        // wrap the exception
                        throw new Error(oops);
                    }
                }
            }
            sclSet = true;
        }
    }
}

其實(shí)getParent()實(shí)際上就是ClassLoader的屬性parent,parent的賦值是在ClassLoader對(duì)象的構(gòu)造方法中,有兩種情況:

  1. 外部類(lèi)指定了ClassLoader時(shí),則ClassLoader的parent就是指定的ClassLoader
  2. 外部類(lèi)未指定時(shí),由getSystemClassLoader()方法生成,結(jié)合前面sun.misc.Laucher源碼的getClassLoader(),返回的是AppCLassLoader。

總結(jié): 一個(gè)ClassLoader創(chuàng)建時(shí)如果沒(méi)有指定parent,那么它的parent默認(rèn)就是AppClassLoader。


我們?cè)诨氐角懊鍸anucher里的關(guān)鍵代碼:

ClassLoader extcl;
extcl = ExtClassLoader.getExtClassLoader();
loader = AppClassLoader.getAppClassLoader(extcl);

前面我已經(jīng)證明了AppClassLoader的parent是ExtClassLoader實(shí)例,而ExtClassLoader并沒(méi)有對(duì)parent賦值。它調(diào)用了父類(lèi)URLClassLoader的構(gòu)造方法并傳遞3個(gè)參數(shù)。

//單參數(shù)構(gòu)造函數(shù)
public ExtClassLoader(File[] dirs) throws IOException {
     super(getExtURLs(dirs), null, factory);   
}
//三參數(shù)構(gòu)造函數(shù)
public  URLClassLoader(URL[] urls, ClassLoader parent,
                          URLStreamHandlerFactory factory) {
     super(parent);
}

這里也就可以證實(shí),ExtClassLoader的parent為null。也能解釋前面為啥ExtClassLoader調(diào)用getParent()時(shí)會(huì)拋錯(cuò)了。

ExtClassLoader既然parent為空,為什么我們還是說(shuō)Boostrap ClassLoader是它的父加載器呢?

Bootstrap ClassLoader是由C++編寫(xiě)的。

Bootstrap ClassLoader是由C/C++編寫(xiě)的,它本身是虛擬機(jī)的一部分,所以它并不是一個(gè)JAVA類(lèi),也就是無(wú)法在java代碼中獲取它的引用,JVM啟動(dòng)時(shí)通過(guò)Bootstrap類(lèi)加載器加載rt.jar等核心jar包中的class文件,之前的int.class,String.class都是由它加載。

JVM初始化sun.misc.Launcher并創(chuàng)建Extension ClassLoader和AppClassLoader實(shí)例。并將ExtClassLoader設(shè)置為AppClassLoader的父加載器。Bootstrap沒(méi)有父加載器,但是它卻可以作為一個(gè)ClassLoader的父加載器。比如ExtClassLoader。這也可以解釋之前通過(guò)ExtClassLoader的getParent方法獲取為Null的現(xiàn)象。

類(lèi)的加載 雙親委托

一個(gè)類(lèi)加載器找class和recource時(shí),是通過(guò)“委托模式”進(jìn)行。它首先判斷這個(gè)class是不是已經(jīng)加載成功,如果有直接返回,如果沒(méi)有就通過(guò)父加載器查找,不斷遞歸,直到Bootstrap ClassLoader,如果Bootstrap找到了,直接返回,如果沒(méi)有一級(jí)級(jí)返回,最后到底自身去查找這些對(duì)象。這種機(jī)制就叫雙親委托。

流程:


藍(lán)色的代表類(lèi)加載器向上委托的方向,如果當(dāng)前的類(lèi)加載器沒(méi)有查詢(xún)到這個(gè)class對(duì)象已經(jīng)加載就請(qǐng)求父加載器(不一定是父類(lèi))進(jìn)行操作,然后以此類(lèi)推。直到Bootstrap ClassLoader。

如果Bootstrap ClassLoader也沒(méi)有加載過(guò)此class實(shí)例,那么它就會(huì)從它指定的路徑中去查找,如果查找成功則返回,如果沒(méi)有查找成功則交給子類(lèi)加載器,也就是ExtClassLoader,這樣類(lèi)似操作直到終點(diǎn),也就是我上圖中的紅色箭頭示例。

  1. 一個(gè)AppClassLoader查找資源時(shí),先看看緩存是否有,緩存有從緩存中獲取,否則委托給父加載器。
  2. 遞歸,重復(fù)第1部的操作。
  3. 如果ExtClassLoader也沒(méi)有加載過(guò),則由Bootstrap ClassLoader出面,它首先查找緩存,如果沒(méi)有找到的話(huà),就去找自己的規(guī)定的路徑下,也就是sun.mic.boot.class下面的路徑。找到就返回,沒(méi)有找到,讓子加載器自己去找。
  4. Bootstrap ClassLoader如果沒(méi)有查找成功,則ExtClassLoader自己在java.ext.dirs路徑中去查找,查找成功就返回,查找不成功,再向下讓子加載器找。
  5. ExtClassLoader查找不成功,AppClassLoader就自己查找,在java.class.path路徑下查找。找到就返回。如果沒(méi)有找到就讓子類(lèi)找,如果沒(méi)有子類(lèi)會(huì)怎么樣?拋出各種異常。

上面的序列,詳細(xì)說(shuō)明了雙親委托的加載流程。委托是從下向上,查找過(guò)程卻是自上至下。

從上面兩張圖可以直觀的看出類(lèi)加載的大致過(guò)程。若要了解更細(xì)點(diǎn)我們還得知道幾個(gè)重要方法loadClass()、findLoadedClass()、findClass()、defineClass()

重要方法 loadClass( )

protected Class<?> loadClass(String name, boolean resolve)
    throws ClassNotFoundException
{
    synchronized (getClassLoadingLock(name)) {
        // 首先,檢測(cè)是否已經(jīng)加載
        Class<?> c = findLoadedClass(name);
        if (c == null) {
            long t0 = System.nanoTime();
            try {
                if (parent != null) {
                    //父加載器不為空則調(diào)用父加載器的loadClass
                    c = parent.loadClass(name, false);
                } else {
                    //父加載器為空則調(diào)用Bootstrap Classloader
                    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. 調(diào)用findLoadedClass(String)去檢測(cè)這個(gè)class是不是已經(jīng)加載過(guò)了
  2. 調(diào)用父加載器loadClass()。若父加載器為null,則jvm內(nèi)置的加載器去替代,也就是Bootstrap ClassLoader。這也解釋ExtClassLoader的parent為null,但卻說(shuō)Bootstrap ClassLoader就是它的父加載器
  3. 如果向上委托父加載器沒(méi)有加載成功,則通過(guò)findClass(String)查找。
    4.如果class在上面的步驟中找到了,參數(shù)resolve又是true的話(huà),那么loadClass()又會(huì)調(diào)用resolveClass(Class)這個(gè)方法來(lái)生成最終的Class對(duì)象

PS:如果要編寫(xiě)一個(gè)classLoader的子類(lèi),也就是自定義一個(gè)classloader,建議覆蓋findClass()方法,而不要直接改寫(xiě)loadClass()方法。

PS:本文整理自以下博客
一看你就懂,超詳細(xì)java中的ClassLoader詳解
若有發(fā)現(xiàn)問(wèn)題請(qǐng)致郵 caoyanglee92@gmail.com

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

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

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