JVM_類加載機制詳解

Class 文件的裝載流程 (類加載過程)

加載 -> 鏈接 (驗證 -> 準備 -> 解析) -> 初始化 -> 使用 -> 卸載

加載

加載階段,jvm 會經過類名獲取到此類的字節碼文件(.class 文件),
而後將該文件中的數據結構轉存到內存裏(轉化爲運行時方法區內的數據結構),
最後在堆中生成一個表明該類的 Class 對象,用於後期使用者建立對象或者調用相關方法。

驗證

驗證階段用於保證 Class 文件符合 jvm 規範,若是驗證失敗會拋出 error。

準備

在該階段虛擬機會給類對象的靜態成員變量配置內存空間,並賦初始值。

解析

將類/接口/字段/方法中的號引用替換爲直接引用。

初始化

虛擬機會調用類對象的初始化方法來進行類變量的賦值。

Class 文件被裝載的條件

必需要有類去主動使用該 Class。
方式有:
使用 new 關鍵字、反射、克隆、反序列化;
調用類的靜態方法;
調用一個類的子類的時候會初始化其父類;
包含 main() 方法的類。

被動使用則不會去裝載 Class。
方式有:
調用了其父類的靜態方法。

總結:java

jvm 秉持了實用主義理念,對於沒有用到的 Class 不會進行裝載。
可是在 java 代碼的啓動環節會加載一些使用到的類。

加載器種類

啓動類加載器(Bootstrap ClassLoader):

在 jdk8 中用來加載 jvm 自身須要的類,c++ 實現,用來加載 rt.jar。
在 jdk9 以後的 jdk 中,Bootstrap ClassLoader 主要用來加載 java.base 中的核心繫統類。

擴展類加載器(ExtClassLoader):

jdk8 中用來加載 ${JAVA_HOME}/lib/ext 目錄下的類。
在 jdk9 中已經被移除。

模塊加載器(PlatformClassLoader):

jdk9 以後用來代替 ExtClassLoader 的加載器,用來加載 jdk 中的非核心模塊類。

應用程序類加載器(AppClassLoader):

用來加載通常的應用類。

自定義加載器:

使用者本身定義的,通常繼承 java.lang.ClassLoader 的類。

雙親委派機制

任意一個 ClassLoader 在嘗試加載一個類的時候,都會先嚐試調用其父類的相關方法去加載類,若是其父類不能加載該類,則交由子類去完成。

這樣的好處:對於任意使用者自定義的 ClassLoader,都會先去嘗試讓 jvm 的 Bootstrap ClassLoader 去嘗試加載(自定義的 ClassLoader 都繼承了它們)。那麼就能保證 jvm 的類會被優先加載,限制了使用者對 jvm 系統的影響。

源碼

源碼探究使用 jdk11,與 jdk8 中的有些許不一樣。c++

ClassLoader

ClassLoader 是類加載器的頂級父類,其核心的方法主要是 loadClass(...) 方法:安全

// ClassLoader.class
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException{
    // 加鎖,保證線程安全
    synchronized (getClassLoadingLock(name)) {
        // 先去找一次 class 是否已經被加載了,若是已經被加載了就不用重複加載了
        // 此方法的核心邏輯由 c++ 實現
        Class<?> c = findLoadedClass(name);
        // 沒有被加載的狀況
        if (c == null) {
            long t0 = System.nanoTime(); // 記錄時間
            try {
                // 此處體現雙親委派機制
                // 若是該加載器存在父加載器,就會先去調用父加載器的相關方法
                // 若是沒有父加載器,就去調用 Bootstrap 加載器
                if (parent != null) {
                    c = parent.loadClass(name, false);
                } else {
                    // 調用 BootstrapClassLoader,此方法的核心邏輯是 c++ 實現的
                    c = findBootstrapClassOrNull(name);
                }
            } catch (ClassNotFoundException e) {

            }

            // 若是依舊加載不到,那麼就說明父加載器仍然加載不到信息
            // 那麼就須要指定的加載器本身去加載了
            if (c == null) {

                long t1 = System.nanoTime();
                
                // 該加載器加載類文件的核心邏輯
                // 該方法在 ClassLoader 中是留空的,須要子類按照自身的邏輯去實現
                c = findClass(name);

                // 此處作一些信息記錄,和主邏輯無關
                PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                PerfCounter.getFindClasses().increment();
            }
        }
        
        if (resolve) {
            // 解析 class,也是留空的,須要子類去實現
            resolveClass(c);
        }
        return c;
    }
}

BuiltinClassLoader

BuiltinClassLoader 是 jdk9 中代替 URLClassLoader 的加載器,是 PlatformClassLoader 與 AppClassLoader 的父類。其繼承了 SecureClassLoader,其核心的方法主要是 loadClassOrNull(...) 方法:數據結構

// BuiltinClassLoader.class

// step 1
@Override
protected Class<?> loadClass(String cn, boolean resolve) throws ClassNotFoundException{
    // 複寫了 loadClass(...) 方法,可是核心是調用 loadClassOrNull(...)
    Class<?> c = loadClassOrNull(cn, resolve);
    if (c == null)
        throw new ClassNotFoundException(cn);
    return c;
}

// step 2
protected Class<?> loadClassOrNull(String cn, boolean resolve) {
    // 加鎖,保證線程安全
    synchronized (getClassLoadingLock(cn)) {
        // 先去找一次 class 是否已經被加載了,此方法是 ClassLoader 中的
        Class<?> c = findLoadedClass(cn);

        if (c == null) {

            // 這裏會須要去先加載模塊信息
            LoadedModule loadedModule = findLoadedModule(cn);
            if (loadedModule != null) {
                BuiltinClassLoader loader = loadedModule.loader();
                if (loader == this) {
                    if (VM.isModuleSystemInited()) {
                        c = findClassInModuleOrNull(loadedModule, cn);
                    }
                } else {
                    c = loader.loadClassOrNull(cn);
                }
            } else {

                // 先調用父加載器的相關方法去加載一次
                if (parent != null) {
                    c = parent.loadClassOrNull(cn);
                }

                // 若是沒加載到,則用當前加載器去加載
                if (c == null && hasClassPath() && VM.isModuleSystemInited(){
                    // 此方法內會調用到 defineClass(...) 方法去加載類文件
                    c = findClassOnClassPathOrNull(cn);
                }
            }

        }

        // 解析 class
        if (resolve && c != null)
            resolveClass(c);

        return c;
    }
}

該加載器中還有一個加載 class 字節碼的方法:app

// BuiltinClassLoader.class
private Class<?> defineClass(String cn, Resource res) throws IOException{
    
    URL url = res.getCodeSourceURL();

    // 先解析這個 class 的路徑
    int pos = cn.lastIndexOf('.');
    if (pos != -1) {
        String pn = cn.substring(0, pos);
        Manifest man = res.getManifest();
        defineOrCheckPackage(pn, man, url);
    }

    // 這裏會將 class 讀取出來成一個 byte[] 字符串,並經過 jvm 的相關方法去加載
    ByteBuffer bb = res.getByteBuffer();
    if (bb != null) {
        CodeSigner[] signers = res.getCodeSigners();
        CodeSource cs = new CodeSource(url, signers);
        // 該方法最後會調用 ClassLoader 內的 native 方法
        return defineClass(cn, bb, cs);
    } else {
        byte[] b = res.getBytes();
        CodeSigner[] signers = res.getCodeSigners();
        CodeSource cs = new CodeSource(url, signers);
        // 該方法最後會調用 ClassLoader 內的 native 方法
        return defineClass(cn, b, 0, b.length, cs);
    }
}

BootClassLoader

BootClassLoader 是 ClassLoaders 的一個靜態內部類,雖然它從代碼實現上是 BuiltinClassLoader 的子類,可是從功能上說它是 PlatformClassLoader 的 parent 類:jvm

// ClassLoader.class
private static class BootClassLoader extends BuiltinClassLoader {
    BootClassLoader(URLClassPath bcp) {
        super(null, null, bcp);
    }

    // 複寫了 BuiltinClassLoader 中的 loadClassOrNull(...) 方法
    @Override
    protected Class<?> loadClassOrNull(String cn) {
        return JLA.findBootstrapClassOrNull(this, cn);
    }
};

PlatformClassLoader

PlatformClassLoader 也是 ClassLoaders 的一個靜態內部類,從功能上說它是 BootClassLoader 的子類,同時也是 AppClassLoader 的 parent 類。PlatformClassLoader 主要用來加載一些 module:ide

// ClassLoader.class
private static class PlatformClassLoader extends BuiltinClassLoader {
    static {
        if (!ClassLoader.registerAsParallelCapable())
            throw new InternalError();
    }

    // 此處會將 BootClassLoader 做爲 parent 參數傳入進去
    PlatformClassLoader(BootClassLoader parent) {
        super("platform", parent, null);
    }

    // 加載 module
    private Package definePackage(String pn, Module module) {
        return JLA.definePackage(this, pn, module);
    }
}

AppClassLoader

AppClassLoader 的核心方法是 loadClass(...),最終會調用到 BuiltinClassLoader.loadClassOrNull(...) 方法,而此方法內部又會調用到 PlatformClassLoader.loadClass(...) 方法;而後實際上 PlatformClassLoader 內部又會去調用 BootClassLoader 的 loadClassOrNull(...) 方法。這種方式下就完成類加載器的雙親委派機制:ui

// ClassLoader.class
private static class AppClassLoader extends BuiltinClassLoader {
    static {
        if (!ClassLoader.registerAsParallelCapable())
            throw new InternalError();
    }

    final URLClassPath ucp;

    // 此處會將 PlatformClassLoader 做爲 parent 參數傳入進去
    AppClassLoader(PlatformClassLoader parent, URLClassPath ucp) {
        super("app", parent, ucp);
        this.ucp = ucp;
    }

    @Override
    protected Class<?> loadClass(String cn, boolean resolve) throws ClassNotFoundException{
        SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            int i = cn.lastIndexOf('.');
            if (i != -1) {
                sm.checkPackageAccess(cn.substring(0, i));
            }
        }
        // 其實是調用了 BuiltinClassLoader.loadClassOrNull(...) 方法
        return super.loadClass(cn, resolve);
    }

    @Override
    protected PermissionCollection getPermissions(CodeSource cs) {
        PermissionCollection perms = super.getPermissions(cs);
        perms.add(new RuntimePermission("exitVM"));
        return perms;
    }


    void appendToClassPathForInstrumentation(String path) {
        ucp.addFile(path);
    }


    private Package definePackage(String pn, Module module) {
        return JLA.definePackage(this, pn, module);
    }


    protected Package defineOrCheckPackage(String pn, Manifest man, URL url) {
        return super.defineOrCheckPackage(pn, man, url);
    }
}
相關文章
相關標籤/搜索