ClassLoader的理解與雙親委派機制和違反雙親委派

這裏大量參考瞭如下博客
https://blog.csdn.net/javazejian/article/details/73413292
ClassLoader是一個抽象類,他有很多個實現
BootstrapClassLoader是最頂層的classloader 負責加載jvm需要的類 <JAVA_HOME>/lib路徑下的核心類庫或-Xbootclasspath參數指定的路徑下的jar包加載到內存中 比如rt.jar 他本身是C++實現的。
ExtClassLoader 是次級的classloader 負責加載<JAVA_HOME>/lib/ext目錄下或者由系統變量-Djava.ext.dir指定位路徑中的類庫,本身是java實現的sun.misc.Launcher$ExtClassLoader。

AppClassLoader 是我們最常用的。它負責加載系統類路徑java -classpath或-D java.class.path 指定路徑下的類庫,本身也是 sun.misc.Launcher$AppClassLoader。
BootstrapClassLoader -》ExtClassLoader -》AppClassLoader 存在這樣的父子關係。
但是這三個類之間本身沒什麼直接繼承關係,他們都是ClassLoader的子類(BootstrapClassLoader除外 它由c++實現),但是通過組合模式 實現了類似父子關係的關係。雙親委派模式
雙親委派模式的實現是依賴於 ClassLoader抽象類定義的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 {     // 如果父爲null 說明已經是BootstrapClassLoader了
                        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();
                   // 通過findClass 加載 類
                    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;
        }
    }

也就是說他們通過非繼承關係實現了類似父子關係。
在這裏插入圖片描述
在這裏插入圖片描述
findClass()方法,方法返回Class對象,他是在loadClass父類無法加載時 由自己加載的方法。ClassLoader本身也沒有實現findClass方法 我們的extClassLoader 和 appClassLoader 都是URLClassLoader的子類 而 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對象   ucp 裏 的path屬性包含了 能夠加載的 類路徑
                        Resource res = ucp.getResource(path, false);
                        if (res != null) {
                            try { 
                                  // 轉換成真正的class對象
                                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;
    }

extClassLoader 能夠加載的目錄
在這裏插入圖片描述appClassLoader能夠加載的目錄
在這裏插入圖片描述
在這裏插入圖片描述
這也就是爲啥靜態覆蓋類能夠生效的原因,因爲classes的加載在 外部jar 包之前。
再來看defineClass

private Class<?> defineClass(String name, Resource res) throws IOException {
        long t0 = System.nanoTime();
        // 取最後一個點的位置
        int i = name.lastIndexOf('.');
         // 獲取到剛纔resource 中的真實url地址
        URL url = res.getCodeSourceURL();
        if (i != -1) {
            // 取到 包名
            String pkgname = name.substring(0, i);
            // Check if package already loaded.
            Manifest man = res.getManifest();
            // 檢查包名的密封性
            definePackageInternal(pkgname, man, url);
        }
        // Now read the class bytes and define the class
      // 從res 中獲取 如果有就用res中的
        java.nio.ByteBuffer bb = res.getByteBuffer();
        if (bb != null) {
            // Use (direct) ByteBuffer:
            CodeSigner[] signers = res.getCodeSigners();
            CodeSource cs = new CodeSource(url, signers);
            sun.misc.PerfCounter.getReadClassBytesTime().addElapsedTimeFrom(t0);
            return defineClass(name, bb, cs);
        } else {
            //獲取類的byte數組
            byte[] b = res.getBytes();
            // must read certificates AFTER reading bytes.
            CodeSigner[] signers = res.getCodeSigners();
            CodeSource cs = new CodeSource(url, signers);
            sun.misc.PerfCounter.getReadClassBytesTime().addElapsedTimeFrom(t0);
            // 真正讀取byte數組並 創建class 對象
            return defineClass(name, b, 0, b.length, cs);
        }
    }

再到ClassLoader抽象類中

protected final Class<?> defineClass(String name, byte[] b, int off, int len,
                                         ProtectionDomain protectionDomain)
        throws ClassFormatError
    {
        // 檢查是否能夠進行加載
        protectionDomain = preDefineClass(name, protectionDomain);
       // 獲取 加載class的 加載地址 比如file:/E:/myWorkSpace/interview/target/classes/
        String source = defineClassSourceLocation(protectionDomain);
         // 加載class  native方法
        Class<?> c = defineClass1(name, b, off, len, protectionDomain, source);
        //加載 後置方法
        postDefineClass(c, protectionDomain);
        return c;
    }
 private ProtectionDomain preDefineClass(String name,
                                            ProtectionDomain pd)
    {
        if (!checkName(name))
            throw new NoClassDefFoundError("IllegalName: " + name);

        // Note: Checking logic in java.lang.invoke.MemberName.checkForTypeAlias
        // relies on the fact that spoofing is impossible if a class has a name
        // of the form "java.*"
        // 包名不能以java開頭
        if ((name != null) && name.startsWith("java.")) {
            throw new SecurityException
                ("Prohibited package name: " +
                 name.substring(0, name.lastIndexOf('.')));
        }
        if (pd == null) {
            pd = defaultDomain;
        }

        if (name != null) checkCerts(name, pd.getCodeSource());

        return pd;
    }

所以loadClass 是實現雙親委派機制 而findClass纔是真正加載類的地方。
ExtClassLoader 和 AppClassLoader 都是Launcher的內部類 他們是在Launcher 的構造方法中創建的
Launcher

public Launcher() {
        // 創建extClassLoader
        Launcher.ExtClassLoader var1;
        try {
            var1 = Launcher.ExtClassLoader.getExtClassLoader();
        } catch (IOException var10) {
            throw new InternalError("Could not create extension class loader", var10);
        }
        // 創建AppClassLoader
        try {
            this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
        } catch (IOException var9) {
            throw new InternalError("Could not create application class loader", var9);
        }
        // 給線程上下文設置ClassLoader 爲 appClassLoader
        Thread.currentThread().setContextClassLoader(this.loader);
        // 這裏設置安全檢查 默認爲 null  不創建
        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);
        }

    }

違反雙親委派
1.調用findClass方法
2.繼承URLClassLoader 如果重寫了 loadClass方法並且 不再實現雙親委派的機制 或者直接調用了 findClass方法
違反雙親委派的用途
1.熱部署
2.自定義地址class加載(從非classPath中加載)
3.SPI
包名爲 java.開頭的類是不允許加載的 這時如果 rt.jar 裏的某個接口需要第三方提供實現(比如sql.Driver) 由於rt.jar 是由 bootstrapClassLoader 加載的 但是它又不能加載第三方jar包,這時怎麼辦 就需要違反雙親委派機制 由 bootstrapClassLoader 委派 appClassLoader來進行加載

//DriverManager是Java核心包rt.jar的類
public class DriverManager {
 //省略不必要的代碼
    static {
        loadInitialDrivers();//執行該方法
        println("JDBC DriverManager initialized");
    }

//loadInitialDrivers方法
 private static void loadInitialDrivers() {
     sun.misc.Providers()
     AccessController.doPrivileged(new PrivilegedAction<Void>() {
            public Void run() {
    //加載外部的Driver的實現類
                ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
                 Iterator<Driver> driversIterator = loadedDrivers.iterator();
                 try{
                    while(driversIterator.hasNext()) {
                        // 實際上在這裏才加載
                        driversIterator.next();
                    }
                } catch(Throwable t) {
                // Do nothing
                }
                return null;
            }
        });
    }

public static <S> ServiceLoader<S> load(Class<S> service) {
        // 這裏就獲取到了我們之前Launcher註冊的 線程上下文加載器  也就是appClassLoader
        ClassLoader cl = Thread.currentThread().getContextClassLoader();
        // 這裏創建了一個ServiceLoader 保存了 信息
        return ServiceLoader.load(service, cl);
    }

// 這裏的next java.util.ServiceLoader#iterator#next
  public S next() {
                if (knownProviders.hasNext())
                    return knownProviders.next().getValue();
                 // 會進入這裏
                return lookupIterator.next();
            }
// 這裏是 java.util.ServiceLoader.LazyIterator #next
 public S next() {
            if (acc == null) {
                //進入這裏
                return nextService();
            } else {
                PrivilegedAction<S> action = new PrivilegedAction<S>() {
                    public S run() { return nextService(); }
                };
                return AccessController.doPrivileged(action, acc);
            }
        }

private S nextService() {
            if (!hasNextService())
                throw new NoSuchElementException();
            String cn = nextName;
            nextName = null;
            Class<?> c = null;
            try {
                c = Class.forName(cn, false, loader);
            } catch (ClassNotFoundException x) {
                fail(service,
                     "Provider " + cn + " not found");
            }
            if (!service.isAssignableFrom(c)) {
                fail(service,
                     "Provider " + cn + " not a subtype");
            }
            try {
                S p = service.cast(c.newInstance());
                providers.put(cn, p);
                return p;
            } catch (Throwable x) {
                fail(service,
                     "Provider " + cn + " could not be instantiated",
                     x);
            }
            throw new Error(); // This cannot happen
        }

在這裏插入圖片描述
在這裏插入圖片描述
這就實現了委託appClassLoader給bootstrapClassLoader加載類信息 也就是爲何提供了違反雙親委派的方法。

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