《Java 虛擬機(jī)原理》7.2 精選 —— 類加載篇

1.描述一下 JVM 加載 class 文件的機(jī)制

說明
Java 中的所有類都需要被類加載器加載到 JVM 中(類加載本身也是一個類,其主要工作是把 class 文件從磁盤讀取到內(nèi)存中)。

1.1 類加載的方式有兩種:

隱式加載,程序運(yùn)行過程中通過 new 指令實例化對象,即把類隱式地加載到 JVM 中;
例子1:加載一個類的時候,類加載會隱式加載它的父類,例如,Child 繼承 Parent,Class.forName("Child") 的時候,會加載 Parent
例子2:執(zhí)行一個類之前,類加載會隱式加載它全部的依賴類(全盤責(zé)任機(jī)制),例如,執(zhí)行 WordCount 的 main 方法之前,JVM 會自動加載 FlatMapFunction、...、WordCountData 等依賴類,也會自動加載 FlatMapFunction 的 Public、Collector、Function 等依賴類

import org.apache.flink.api.common.functions.FlatMapFunction;
import org.apache.flink.streaming.api.datastream.DataStream;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.streaming.examples.wordcount.util.WordCountData;

public class WordCount {

    public static void main(String[] args) throws Exception {
     // 忽略
    }
}
import org.apache.flink.annotation.Public;
import org.apache.flink.util.Collector;
import org.apache.flink.api.common.functions.Function;

public interface FlatMapFunction<T, O> extends Function, Serializable {
    // 忽略
}

全盤責(zé)任機(jī)制
當(dāng)一個 ClassLoader 裝載一個類時,除非顯式另一個 ClassLoader,該類所依賴的類也由這個 ClassLoader 負(fù)責(zé)加載。

顯示加載,通過反射 Class.forName() 等方法,把類顯式地加載到 JVM 中;

this.getclass().getClassLoader().loadClass();
Class.forName("WordCount");

1.2 類加載器的模型及其類型

類加載器采用雙親委派模型, 其類型有 4 種:
Bootstrap ClassLoader 啟動類加載器:負(fù)責(zé)加載系統(tǒng)類和 /lib 目錄的 jar 和類,例如 String
ExtClassLoader 擴(kuò)展類加載器:負(fù)責(zé)加載 /lib/ext 目錄下的 jar 和類
AppClassLoader 應(yīng)用程序類加載器:負(fù)責(zé)加載當(dāng)前應(yīng)用 ClassPath 的 jar 和類
UserDefinedClassLoader 用戶自定義加載器:負(fù)責(zé)加載用戶自定義的 jar 和類

2.3 類加載的過程

系統(tǒng)加載 Class 類型的文件主要三步:加載->連接->初始化。連接過程又可分為三步:驗證->準(zhǔn)備->解析

image.png

2.JVM 為什么采用雙親委派模型

雙親委派模型是 Java 類加載器的工作機(jī)制

(1)雙親委派模型的工作原理

雙親委派模型是指如果一個類加載器收到了類加載請求,它并不會自己先去加載,而是把這個請求委托給父類的加載器去執(zhí)行,如果父類加載器還存在其父類加載器,則進(jìn)一步向上委托,依次遞歸,請求最終將到達(dá)頂層的啟動類加載器,如果父類加載器可以完成類加載任務(wù),就成功返回,倘若父類加載器無法完成此加載任務(wù),子加載器才會嘗試自己去加載。
注意:雙親委派模式中的父子關(guān)系并非通常所說的類繼承關(guān)系,而是采用組合關(guān)系來復(fù)用父類加載器。

image.png

雙親委派模型的關(guān)鍵代碼

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;
        }
    }
1614343160.png

(2)采用雙親委派機(jī)制的優(yōu)點

避免重復(fù)加載類。注意:相同的類文件,被不同類加載器加載產(chǎn)出的是兩個不同的類。
避免 Java 核心 API 被篡改,保證程序穩(wěn)定運(yùn)行。用戶自定義編寫 java.lang.Object 類,如果沒有雙親委派模型,每個類加載器加載各自的類,導(dǎo)致系統(tǒng)出現(xiàn)多個不同的 Object 類。

(3)如何實現(xiàn)熱加載類

參考 Flink 的 child-first 類加載機(jī)制

public final class ChildFirstClassLoader extends FlinkUserCodeClassLoader {

    // 省略構(gòu)造函數(shù)等

    @Override
    protected synchronized Class<?> loadClassWithoutExceptionHandling(
            String name,
            boolean resolve) throws ClassNotFoundException {

        // 首先, 檢查該類是否被加載
        Class<?> c = findLoadedClass(name);

        if (c == null) {
            // 省略...

            try {
                // 根據(jù) URL 選擇類
                c = findClass(name);
            } catch (ClassNotFoundException e) {
                // let URLClassLoader do it, which will eventually call the parent
                c = super.loadClassWithoutExceptionHandling(name, resolve);
            }
        }

        // 省略...
    }
        
    // 重寫獲取資源路徑, 避免使用父加載器的 getResource 方法
    @Override
    public URL getResource(String name) {
        // 使用 URLClassloader 的 getResource 
        URL urlClassLoaderResource = findResource(name);

        if (urlClassLoaderResource != null) {
            return urlClassLoaderResource;
        }

        // delegate to super
        return super.getResource(name);
    }
    // 重寫獲取資源路徑, 避免使用父加載器的 getResources 方法
    @Override
    public Enumeration<URL> getResources(String name) throws IOException {
        // f使用 URLClassloader 的 getResources
        Enumeration<URL> urlClassLoaderResources = findResources(name);

        final List<URL> result = new ArrayList<>();

        while (urlClassLoaderResources.hasMoreElements()) {
            result.add(urlClassLoaderResources.nextElement());
        }

        // 省略...
    }
}

ChildFirstClassLoader 的核心代碼是 loadClassWithoutExceptionHandling 方法,沒有采用父加載器 findClass(避免采用雙親委派模型),而是采用自定義加載器 URLClassLoader 的findClass,同時重寫 getResource 方法,即使用 URLClassLoader 獲取資源的 URL。

3.如何判斷一個類使無用的類

無用的類”需要滿足一下條件:
該類所有的實例都已經(jīng)被回收,即 JVM Heap 里不存在該類的任何實例;
該類對應(yīng)的 java.lang.Class 對象沒有在任何地方被引用,無法在任何地方通過反射訪問該類的方法;
加載該類的 ClassLoader 已經(jīng)被回收。

注意:滿足上述條件,“可以”對該對象進(jìn)行回收,而不是“馬上”、“必然”回收。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容