URLClassLoader詳解

URLClassLoader詳解

ClassLoader翻譯過來就是類加載器,普通的java開發者其實用到的不多,但對於某些框架開發者來說卻非常常見。理解ClassLoader的加載機制,也有利於我們編寫出更高效的代碼。ClassLoader的具體作用就是將class文件加載到jvm虛擬機中去,程序就可以正確運行了。但是,jvm啓動的時候,並不會一次性加載所有的class文件,而是根據需要去動態加載。想想也是的,一次性加載那麼多jar包那麼多class,那內存不崩潰。本文的目的也是學習ClassLoader這種加載機制。。

  • java類加載機制原理
  • 雙親加載機制的優劣

java類加載機制

ClassLoader結構


ClassLoader結構

java中內置了很多類加載器,本文只討論幾個核心類加載器:

ClassLoader:所有類加載器的基類,它是抽象的,定義了類加載最核心的操作。所有繼承與classloader的加載器,都會優先判斷是否被父類加載器加載過,防止多次加載,防止加載衝突。。。

protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
    //鎖,防止多次加載,所以jvm啓動巨慢
        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;
        }
    }

jdk 1.7爲了提供並行加載class,提供ClassLoader.ParallelLoaders內部類,用來封裝一組並行能力的加載器類型。這個一般是用不到的,有興趣可以先看一下。但是需要知道ClassLoader是支持並行加載的。

    private static class ParallelLoaders

Bootstrap classLoader:位於java.lang.classload,所有的classload都要經過這個classload判斷是否已經被加載過,採用native code實現,是JVM的一部分,主要加載JVM自身工作需要的類,如java.lang.、java.uti.等; 這些類位於$JAVA_HOME/jre/lib/rt.jar。Bootstrap ClassLoader不繼承自ClassLoader,因爲它不是一個普通的Java類,底層由C++編寫,已嵌入到了JVM內核當中,當JVM啓動後,Bootstrap ClassLoader也隨着啓動,負責加載完核心類庫後,並構造Extension ClassLoader和App ClassLoader類加載器。

  /**
     * Returns a class loaded by the bootstrap class loader;
     * or return null if not found.
     */
    private Class<?> findBootstrapClassOrNull(String name)
    {
        if (!checkName(name)) return null;

        return findBootstrapClass(name);
    }

    // return null if not found
    private native Class<?> findBootstrapClass(String name);

SecureClassLoader:繼承自ClassLoader,添加了關聯類源碼、關聯繫統policy權限等支持。

    public class SecureClassLoader extends ClassLoader

URLClassLoader:繼承自SecureClassLoader,支持從jar文件和文件夾中獲取class,繼承於classload,加載時首先去classload裏判斷是否由bootstrap classload加載過,1.7 新增實現closeable接口,實現在try 中自動釋放資源,但撲捉不了.close()異常

public class URLClassLoader extends SecureClassLoader implements Closeable

ExtClassLoader:擴展類加載器,繼承自URLClassLoader繼承於urlclassload,擴展的class loader,加載位於$JAVA_HOME/jre/lib/ext目錄下的擴展jar。查看源碼可知其查找範圍爲System.getProperty(“java.ext.dirs”)。

public static Launcher.ExtClassLoader getExtClassLoader() throws IOException {
            //System.getProperty("java.ext.dirs");
            //在項目啓動時就加載所有的ext.dirs目錄下的文件,並將其初始化
            final File[] var0 = getExtDirs();

            try {
            //AccessController.doPrivileged特權,讓程序突破當前域權限限制,臨時擴大訪問權限
                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 比較有意思的是,他使用的是頂級類(classloader)的loadclass方法,並沒有重寫,而且他的父親加載器是null。。 。

public ExtClassLoader(File[] var1) throws IOException {
//parents is null
            super(getExtURLs(var1), (ClassLoader)null, Launcher.factory);
            SharedSecrets.getJavaNetAccess().getURLClassPath(this).initLookupCache(this);
        }

AppClassLoader:應用類加載器,繼承自URLClassLoader,也叫系統類加載器(ClassLoader.getSystemClassLoader()可得到它),它負載加載應用的classpath下的類,查找範圍System.getProperty(“java.class.path”),通過-cp或-classpath指定的類都會被其加載,沒有完全遵循雙親委派模型的,它重的是loadClass方法

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

            //ucp是SharedSecrets獲取的Java棧幀中存儲的類信息
            if (this.ucp.knownToNotExist(var1)) {
            //頂級類classloader加載的信息
                Class var5 = this.findLoadedClass(var1);
                if (var5 != null) {
                    if (var2) {
                    //link過程,Class載入必須link,link指的是把單一的Class加入到有繼承關係的類樹中,不link一切都無從談起了
                        this.resolveClass(var5);
                    }

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

Launcher:java程序入口,負責實例化相關class,ExtClassLoader和AppClassLoader都是其內部實現類。。。

首先獲取bootClassPath 的位置信息,交付於父類Classloader去加載位於$JAVA_HOME/jre/lib/rt.jar 的類

private static String bootClassPath = System.getProperty("sun.boot.class.path");
public static URLClassPath getBootstrapClassPath() {
        return Launcher.BootClassPathHolder.bcp;
    }

實例化相關ExtClassLoader和AppClassLoader,單線程運行,內部加載會對class文件加鎖(所以啓動慢的原因??)

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

        try {
        //實例化AppClassLoader,並把ExtClassLoader置爲父類
            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);
        }

    }

ClassLoader運行結構

主要基於 雙親加載機制

classload運行結構

雙親加載機制:主要體現在ClassLoader的loadClass()方法中,思路很簡單:先檢查是否已經被加載過,若沒有加載則調用父類加載器的loadClass()方法,若父類加載器爲空則默認使用啓動類加載器作爲父類加載器。如果父類加載器加載失敗,拋出ClassNotFoundException異常後,調用自己的findClass()方法進行加載。

雙親與判斷等於:一般自定義的Class Loader可以從java.lang.ClassLoader繼承,不同classloader加載相同的類,他們在內存也不是相等的,即它們不能互相轉換,會直接拋異常。java.lang.ClassLoader的核心加載方法是loadClass方法

     Class  clazz = null; 
ClassLoader classLoader;
try {
classLoader =  new SpecialClassLoader ();
clazz = classLoader.loadClass("Hello");
System.out.println(clazz);
System.out.println(clazz.getClassLoader());
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
Class helloClazz;
helloClazz = Hello.class;
System.out.println("helloClazz:" + helloClazz);
System.out.println(helloClazz.getClassLoader());
System.out.println(helloClazz == clazz);
System.out.println(helloClazz.equals(clazz));

運行結果:

loadClass:Hello

specialLoadClass:test/Hello.class

loadClass:java.lang.Object

—resolveClass–

class com.test.javatechnology.classloader.test.Hello

com.test.javatechnology.classloader.SpecialClassLoader@106d69c

helloClazz:class com.test.javatechnology.classloader.test.Hello

sun.misc.Launcher$AppClassLoader@e2f2a

false

false

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