1. 程式人生 > >Java-類載入機制

Java-類載入機制

Java-類載入機制

摘要

本文簡要介紹Java載入機制,還會介紹雙親委派機制的破壞,執行緒上下文載入器,以及JDBC Driver是如何自動載入的。

未完成

0x01 Java類載入機制

1.1 簡介

當前版本jdk是採用雙親委派機制:

類載入機制

子ClassLoader總是會讓父ClassLoader嘗試載入,如果不行,才會自己嘗試載入。

1.1.1 BootstrapClassLoader

BootstrapClassLoader是啟動類載入器。

通過以下程式碼打出BootstrapClassLoader載入的檔案:

public class
BootStrapTest { public static void main(String[] args) { URL[] urls = sun.misc.Launcher.getBootstrapClassPath().getURLs(); for (int i = 0; i < urls.length; i++) { System.out.println(urls[i].toExternalForm()); } } }

結果如下:

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

可以看到除了rt.jar以外還載入了一些其他檔案。

1.2 雙親委派的意義

比如java.lang.Object,使用者也自定義一個同樣許可權定名的類。但在載入時,會首先用BootstrapClassLoader載入rt.jar中的該類。而使用者自定義的Object類,也會因為AppClassLoader往上尋找到祖先BootstrapClassLoader類來載入該類,但會發現該類已經被載入過導致報錯。

0x02 雙親委派機制的破壞

2.1 破壞1

JDK1.2之前沒有雙親委派模型,所以之前的開發者繼承java.lang.ClassLoader後要做的就是重寫loadClass()

方法,編寫自定義的類載入邏輯。該loadClass方法程式碼如下:

// 父載入器
private final ClassLoader parent;
protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        // 先在ClassLoader獲取該ClassName對應的同步鎖物件
        synchronized (getClassLoadingLock(name)) {
            // First, check if the class has already been loaded
            // 先檢查該類全限定名是否已經被載入到JVM
            Class<?> c = findLoadedClass(name);
            if (c == null) {
            // 此時沒有載入該類
                long t0 = System.nanoTime();
                try {
                    if (parent != null) {
                    // 存在父載入器(不是BootStrapClassLoader)
                        // 就嘗試讓父載入器載入類,但不連線
                        c = parent.loadClass(name, false);
                    } else {
                        // 否則嘗試用BootStrapClassLoader載入該類
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                }

                if (c == null) {
                // 此時還沒有載入該類
                    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;
        }
    }

但在JDK1.2之後,不再提倡重寫loadClass(),而是應該重寫新加入的findClass方法:

protected Class<?> findClass(String name) throws ClassNotFoundException {
	// 該方法預設沒有實現,只是丟擲一個ClassNotFoundException
    throw new ClassNotFoundException(name);
}

啟動類載入器BootstrapClassLoader是用C++實現,而擴充套件類載入器ExtClassLoader和應用程式類載入器AppClassLoader都繼承自URLClassLoaderfindClass方法直接用的URLClassLoaderfindClass方法:

// 要載入的類的全限定名
// 比如是demos.classInitialization.classloader.order.EntityC
protected Class<?> findClass(final String name)
        throws ClassNotFoundException
{
    final Class<?> result;
    try {
        result = AccessController.doPrivileged(
            new PrivilegedExceptionAction<Class<?>>() {
                public Class<?> run() throws ClassNotFoundException {
                    // 那麼這裡path為demos/classInitialization/classloader/order/EntityC.class
                    String path = name.replace('.', '/').concat(".class");
                    Resource res = ucp.getResource(path, false);
                    if (res != null) {
                        try {
                            // 使用從指定Resource獲取的類位元組來轉為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) {
        // 如果結果為空,直接拋ClassNotFoundException
        throw new ClassNotFoundException(name);
    }
    // 返回得到的Class物件
    return result;
}

接著看看URLClassLoaderdefineClass方法:

/*
 * 使用從指定的資源中獲取的class bytes 來定義一個Class物件
 * 最終得到的Class必須在使用前被解析
 */
private Class<?> defineClass(String name, Resource res) throws IOException {
    long t0 = System.nanoTime();
    int i = name.lastIndexOf('.');
    // file:/xxx/javaDemos/target/classes/
    URL url = res.getCodeSourceURL();
    if (i != -1) {
        String pkgname = name.substring(0, i);
        // 檢查包是否已經載入過
        Manifest man = res.getManifest();
        // 
        definePackageInternal(pkgname, man, url);
    }
    // 從class位元組碼中讀取資料並轉為Class
    java.nio.ByteBuffer bb = res.getByteBuffer();
    if (bb != null) {
        // 直接使用ByteBuffer讀取(位元組緩衝)
        CodeSigner[] signers = res.getCodeSigners();
        CodeSource cs = new CodeSource(url, signers);
        sun.misc.PerfCounter.getReadClassBytesTime().addElapsedTimeFrom(t0);
        return defineClass(name, bb, cs);
    } else {
        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);
        return defineClass(name, b, 0, b.length, cs);
    }
}

2.2 破壞2-執行緒上下文載入器

用父載入器載入的無法直接去載入子載入器的內容。

2.3 破壞3-OSGI