類加載器簡介

一、ClassLoader簡介

               Java的類加載器的作用就是在運行時加載類到虛擬機中,首先不管對於什麼樣的java應用肯定是由很多class組成實現的,不同的功能所在的class是不一樣的(你說我把所有的功能放在一個class裏面,那你很棒棒哦),比如當我們的入口函數被調用的時候,入口函數使用了其他class(靜態代碼塊、實例)的功能,這時類加載器就會按需加載將需要的類加載進內存中,類加載器不是一次性全部class加載到內存中,而是按需加載。類加載器加載class的原則是:委託、可見性和單一性。

 單一性:單一性原理是指僅加載一個類一次,這是由委託機制確保子類加載器不會再次加載父類加載器加載過的類。

 可見性:可見性是指子類的加載器可以看見所有的父類加載器加載的類,而父類加載器看不到子類加載器加載的類 。     

 委託:這裏重點說下雙親委託機制,說這個之前我們先看下jvm定義的類加載器:

Bootstrap ClassLoader: 最頂層的加載類,主要加載核心類庫,%JRE_HOME%\lib下的rt.jar、resources.jar、charsets.jar和class等等。另:我們是可以通過啓動jvm時指定-Xbootclasspath和路徑來改變Bootstrap ClassLoader的加載目錄。
Extention ClassLoader: 擴展的類加載器,加載目錄%JRE_HOME%\lib\ext目錄下的jar包和class文件。
AppclassLoader:        也稱爲SystemAppClass 加載當前應用的classpath的所有類。

           除了以上3個jvm定義的類加載器,我們也可以自定義類加載器(通過繼承ClassLoader這裏就不說了)。這裏的父類加載器不是繼承關係,而是持有一個應用!類的加載流程用一句話簡單說就是向上委託,向下查找加載。流程如下:

 

二、類加載器的委託加載

          看了這3個類加載器加載路徑和執行順序後,爲了更好的理解,我們從代碼方面看下具體的加載路徑和加載順序。我們先看下sun.misc.Launcher,它是java程序的入口,這個類的代碼結構如下:
 

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);
    }
    //設置AppClassLoader爲線程上下文類加載器
    Thread.currentThread().setContextClassLoader(loader);
}
static class AppClassLoader extends URLClassLoader {	}

static class ExtClassLoader extends URLClassLoader{}

public ClassLoader getClassLoader() {
   return this.loader;
}

從代碼看出,Launcher在構造器中初始化了ExtClassLoader和AppClassLoader,但是沒有Bootstrap類加載器的信息。但是有個路徑:sun.boot.class.path,這就是BootstrapClassLoader加載jar的路徑,我們看下BootstrapClassLoader加載哪些jar或classes:

public static void main(String[] args) {
    System.out.println(System.getProperty("sun.boot.class.path"));
}

/Library/Java/JavaVirtualMachines/jdk1.8.0_131.jdk/Contents/Home/jre/lib/resources.jar
/Library/Java/JavaVirtualMachines/jdk1.8.0_131.jdk/Contents/Home/jre/lib/rt.jar
/Library/Java/JavaVirtualMachines/jdk1.8.0_131.jdk/Contents/Home/jre/lib/sunrsasign.jar
/Library/Java/JavaVirtualMachines/jdk1.8.0_131.jdk/Contents/Home/jre/lib/jsse.jar
/Library/Java/JavaVirtualMachines/jdk1.8.0_131.jdk/Contents/Home/jre/lib/jce.jar
/Library/Java/JavaVirtualMachines/jdk1.8.0_131.jdk/Contents/Home/jre/lib/charsets.jar
/Library/Java/JavaVirtualMachines/jdk1.8.0_131.jdk/Contents/Home/jre/lib/jfr.jar
/Library/Java/JavaVirtualMachines/jdk1.8.0_131.jdk/Contents/Home/jre/classes

BootstrapClassLoader因爲不是Java寫的類加載器,所以在這裏是看不到的,但是它也是要去加載類的,通過BootClassPathHolder類可以發現其加載範圍是由System.getProperty("sun.boot.class.path")決定的。上面我們也看到了其加載的路徑了。

ExtClassLoader類加載器,我們首先看下ExtClassLoader的代碼:

static class ExtClassLoader extends URLClassLoader {
    //The class loader used for loading installed extensions.
    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();
        }
    }
    //獲取加載的路徑
    private static File[] getExtDirs() {
        String var0 = System.getProperty("java.ext.dirs");
        File[] var1;
        if (var0 != null) {
            StringTokenizer var2 = new StringTokenizer(var0, File.pathSeparator);
            int var3 = var2.countTokens();
            var1 = new File[var3];
            for(int var4 = 0; var4 < var3; ++var4) {
                var1[var4] = new File(var2.nextToken());
            }
        } else {
            var1 = new File[0];
        }
        return var1;
    }
    static {
        ClassLoader.registerAsParallelCapable();
    }
    .....省略
}

這裏我們又看到了一個路徑 java.ext.dirs,這個是ExtClassLoader的加載路徑,我們看下它是加載了哪些路徑:

public static void main(String[] args) {
    System.out.println(System.getProperty("java.ext.dirs"));
}
/Library/Java/JavaVirtualMachines/jdk1.8.0_131.jdk/Contents/Home/jre/lib/ext
/Network/Library/Java/Extensions
/System/Library/Java/Extensions

再看看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);
        }
    }

    static {
        ClassLoader.registerAsParallelCapable();
    }
}

可以看到AppClassLoader加載的就是java.class.path下的路徑。我們同樣打印它的值。

public static void main(String[] args) {
    System.out.println(System.getProperty("java.class.path"));
}
//這裏打印出來很多的,我這裏只是粘貼出來我們當前運行的這個工程
/Users/pengjian/work/workspace/pepsi/pepsi-master/tomcat-demo/target/classes

現在3個類加載器已知曉其加載的路徑了,下面我們用事例來看下它的加載順序是什麼。先看一個簡單的代碼片段:

public class SimpleClass {
    
}

public class ClassLoaderTest {
    public static void main(String[] args) {
        //當前類加載器
        ClassLoader cl = SimpleClass.class.getClassLoader();
        System.out.println(cl);
        //父類加載器
        ClassLoader parentCLoader= cl.getParent();
        System.out.println(parentCLoader);
        //父類的父類
        ClassLoader grandParentCl= parentCLoader.getParent();
        System.out.println(grandParentCl);
    }
}
輸出:
sun.misc.Launcher$AppClassLoader@18b4aac2
sun.misc.Launcher$ExtClassLoader@23fc625e
null

這裏不難看出,當前SimpleClass是我的應用中的類,是應該有AppClassLoader加載,通過輸出也可以看出,其AppClassLoader的父類加載器是ExtClassLoader。但是爲什麼ExtClassLoader的父類是null呢?我們看下Launcher中的代碼:

ClassLoader extcl;
//沒有設置父類
extcl = ExtClassLoader.getExtClassLoader();
//設置父類加載器是extcl
loader = AppClassLoader.getAppClassLoader(extcl);

          這裏先說下AppClassLoader和ExtClassLoader,有個父類URLClassLoader,ExtClassLoader在調用 父類構造方法的時候傳入的父類加載器爲null,所以我們這裏輸出出來的ExtClassLoader父類加載器爲null。可能有人要問了,既然是null爲什麼可以當作ExtClassLoader的父類加載器去加載java_home/lib下面的jar和classes?
           因爲Bootstrap ClassLoader是由C/C++編寫的,它本身是虛擬機的一部分,所以它並不是一個JAVA類,也就是無法在java代碼中獲取它的引用。
          我們在從代碼層面去看當需要Bootstrap ClassLoader加載的時候是怎麼loadClass的,先看下loadClass方法代碼:

protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException{
    synchronized (getClassLoadingLock(name)) {
        // First, check if the class has already been loaded
        Class<?> c = findLoadedClass(name);
        if (c == null) {
            long t0 = System.nanoTime();
            try {
                if (parent != null) {
                    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();
            }
        }
        if (resolve) {
            resolveClass(c);
        }
        return c;
    }
}

這段代碼基本上解釋了上面所有的問題。
1、先是執行findLoadedClass(String)去檢測這個class是不是已經加載過了。 
2、執行父加載器的loadClass方法。如果父加載器爲null,則jvm內置的加載器去替代,也就是Bootstrap ClassLoader。這也解釋了ExtClassLoader的parent爲null,但仍然說Bootstrap ClassLoader是它的父加載器,這裏最終加載方法是一個本地方法。 
3、如果向上委託父加載器沒有加載成功,則通過findClass(String)查找。
總結一下類加載器:
1、java 定義了3個ClassLoader,用於加載不同路徑的class類。
2、ClassLoader通過雙親委託來按需加載指定路徑下的class和資源。 
3、另外,JVM在判定兩個class是否相同時,不僅要判斷兩個類名是否相同,而且要判斷是否由同一個類加載器實例加載的。只有兩者同時滿足的情況下,JVM才認爲這兩個class是相同的。           
下面一篇我們將說明一下自定義類加載器和tomcat的類加載器。 

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