JVM真香系列:輕鬆理解class文件到虛擬機(下)

關注「Java後端技術全棧」java

回覆「000」獲取大量電子書面試

類加載器

類加載器是不少人認爲很硬的骨頭。其實也沒那麼可怕,請聽老田慢慢道來。編程

在裝載(Load)階段,經過類的全限定名獲取其定義的二進制字節流,須要藉助類裝載器完成,顧名思義,就是用來裝載Class文件的。

上面咱們自定義一個String出了問題,問題在於JVM不知道咱們想用哪一個類,因而JVM就定義了個規範。後端

把這種類裝載器分紅幾類。緩存

Bootstrap ClassLoader

負責加載$JAVA_HOME中 jre/lib/rt.jar裏全部的class或Xbootclassoath選項指定的jar包。由C++實現,不是ClassLoader子類。tomcat

Extension ClassLoader

負責加載Java平臺中擴展功能的一些jar包,包括$JAVA_HOME中jre/lib/*.jar-Djava.ext.dirs指定目錄下的jar包。網絡

App ClassLoader

負責加載classpath中指定的jar包及 Djava.class.path所指定目錄下的類和jar包。架構

Custom ClassLoader

經過java.lang.ClassLoader的子類自定義加載class,屬於應用程序根據自身須要自定義的ClassLoader,如tomcatjboss都會根據j2ee規範自行實現ClassLoader框架

圖解類加載

加載原則

檢查某個類是否已經加載:順序是自底向上,從Custom ClassLoaderBootStrap ClassLoader逐層檢查,只要某個Classloader已加載,就視爲已加載此類,保證此類只全部ClassLoader加載一次。ide

加載的順序:加載的順序是自頂向下,也就是由上層來逐層嘗試加載此類。

ClassLoader類分析

java.lang.ClassLoader中很重要的三個方法:

loadClass方法

findClass方法

defineClass方法

loadClass方法
1    public Class<?> loadClass(String name) throws ClassNotFoundException {
 2        return loadClass(name, false);
 3    }
 4    protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException{
 5        //使用了同步鎖,保證不出現重複加載
 6        synchronized (getClassLoadingLock(name)) {
 7            // 首先檢查本身是否已經加載過
 8            Class<?> c = findLoadedClass(name);
 9            //沒找到
10            if (c == null) {
11                long t0 = System.nanoTime();
12                try {
13                    //有父類
14                    if (parent != null) {
15                        //讓父類去加載
16                        c = parent.loadClass(name, false);
17                    } else {
18                        //若是沒有父類,則委託給啓動加載器去加載
19                        c = findBootstrapClassOrNull(name);
20                    }
21                } catch (ClassNotFoundException e) {
22                    // ClassNotFoundException thrown if class not found
23                    // from the non-null parent class loader
24                }
25
26                if (c == null) {
27                    // If still not found, then invoke findClass in order
28                    // to find the class.
29                    long t1 = System.nanoTime();
30                    // 若是都沒有找到,則經過自定義實現的findClass去查找並加載
31                    c = findClass(name);
32
33                    // this is the defining class loader; record the stats
34                    sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
35                    sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
36                    sun.misc.PerfCounter.getFindClasses().increment();
37                }
38            }
39            //是否須要在加載時進行解析
40            if (resolve) {
41                resolveClass(c);
42            }
43            return c;
44        }
45    }

正如loadClass方法所展現的,當類加載請求到來時,先從緩存中查找該類對象,若是存在直接返回,若是不存在則交給該類加載去的父加載器去加載,假若沒有父加載則交給頂級啓動類加載器去加載,最後假若仍沒有找到,則使用findClass()方法去加載(關於findClass()稍後會進一步介紹)。

loadClass實現也能夠知道,若是不想從新定義加載類的規則,也沒有複雜的邏輯,只想在運行時加載本身指定的類,那麼咱們能夠直接使用this.getClass().getClassLoder.loadClass("className"),這樣就能夠直接調用ClassLoaderloadClass方法獲取到class對象。

findClass方法
1    protected Class<?> findClass(String name) throws ClassNotFoundException {
2        throw new ClassNotFoundException(name);
3    }

JDK1.2以前,在自定義類加載時,總會去繼承ClassLoader類並重寫loadClass方法,從而實現自定義的類加載類,可是在JDK1.2以後已再也不建議用戶去覆蓋loadClass()方法,而是建議把自定義的類加載邏輯寫在findClass()方法中。

從前面的分析可知,findClass()方法是在loadClass()方法中被調用的,當loadClass()方法中父加載器加載失敗後,則會調用本身的findClass()方法來完成類加載,這樣就能夠保證自定義的類加載器也符合雙親委託模式。

須要注意的是,ClassLoader類中並無實現findClass()方法的具體代碼邏輯,取而代之的是拋出ClassNotFoundException異常。同時應該知道的是,findClass方法一般是和defineClass方法一塊兒使用的。

defineClass方法
1    protected final Class<?> defineClass(String name, byte[] b, int off, int len,
2                            ProtectionDomain protectionDomain) throws ClassFormatError{
3        protectionDomain = preDefineClass(name, protectionDomain);
4        String source = defineClassSourceLocation(protectionDomain);
5        Class<?> c = defineClass1(name, b, off, len, protectionDomain, source);
6        postDefineClass(c, protectionDomain);
7        return c;
8    }

defineClass()方法是用來將byte字節流解析成JVM可以識別的Class對象。

經過這個方法不只可以經過class文件實例化class對象,也能夠經過其餘方式實例化class對象,如經過網絡接收一個類的字節碼,而後轉換爲byte字節流建立對應的Class對象 。

如何自定義類加載器

用戶根據需求本身定義的。須要繼承自ClassLoader,重寫方法findClass()

若是想要編寫本身的類加載器,只須要兩步:

  • 繼承ClassLoader
  • 覆蓋findClass(String className)方法

**ClassLoader**超類的loadClass方法用於將類的加載操做委託給其父類加載器去進行,只有當該類還沒有加載而且父類加載器也沒法加載該類時,才調用findClass方法。
若是要實現該方法,必須作到如下幾點:

1.爲來自本地文件系統或者其餘來源的類加載其字節碼。
2.調用ClassLoader超類的defineClass方法,向虛擬機提供字節碼。

淺談雙親委派模型

這個在面試中也是頻率至關高。

若是一個類加載器在接到加載類的請求時,先查找是否已經加載過,若是沒有被加載過,它首先不會本身嘗試去加載這個類,而是把這個請求任務委託給父類加載器去完成,依次遞歸。

若是父類加載器能夠完成類加載任務,就成功返回;只有父類加載器沒法完成此加載任務時,才本身去加載。

優點

Java類隨着加載它的類加載器一塊兒,具有了一種帶有優先級的層次關係。

好比,Java中的Object類,它存放在rt.jar之中,不管哪個類加載器要加載這個類,最終都是委派給處於模型最頂端的啓動類加載器進行加載,所以Object在各類類加載環境中都是同一個類。

若是不採用雙親委派模型,那麼由各個類加載器本身取加載的話,那麼系統中會存在多種不一樣的Object類。

打破雙親委派模型的案例

tomcat

tomcat 經過 war 包進行應用的發佈,它實際上是違反了雙親委派機制原則的。簡單看一下 tomcat 類加載器的層次結構。

對於一些須要加載的非基礎類,會由一個叫做 WebAppClassLoader 的類加載器優先加載。等它加載不到的時候,再交給上層的 ClassLoader 進行加載。這個加載器用來隔毫不同應用的 .class 文件,好比你的兩個應用,可能會依賴同一個第三方的不一樣版本,它們是相互沒有影響的。

如何在同一個 JVM 裏,運行着不兼容的兩個版本,固然是須要自定義加載器才能完成的事。

那麼 tomcat 是怎麼打破雙親委派機制的呢?

能夠看圖中的 WebAppClassLoader,它加載本身目錄下的 .class 文件,並不會傳遞給父類的加載器。可是,它卻可使用 SharedClassLoader 所加載的類,實現了共享和分離的功能。

可是你本身寫一個 ArrayList,放在應用目錄裏,tomcat 依然不會加載。它只是自定義的加載器順序不一樣,但對於頂層來講,仍是同樣的。

OSGi

OSGi 曾經很是流行,Eclipse 就使用 OSGi 做爲插件系統的基礎。

OSGi 是服務平臺的規範,旨在用於須要長運行時間、動態更新和對運行環境破壞最小的系統。

OSGi 規範定義了不少關於包生命週期,以及基礎架構和綁定包的交互方式。這些規則,經過使用特殊 Java 類加載器來強制執行,比較霸道。

好比,在通常 Java 應用程序中,classpath 中的全部類都對全部其餘類可見,這是毋庸置疑的。可是,OSGi 類加載器基於 OSGi 規範和每一個綁定包的 manifest.mf 文件中指定的選項,來限制這些類的交互,這就讓編程風格變得很是的怪異。但咱們不難想象,這種與直覺相違背的加載方式,確定是由專用的類加載器來實現的。

隨着 jigsaw 的發展(旨在爲 Java SE 平臺設計、實現一個標準的模塊系統),我我的認爲,如今的 OSGi,意義已經不是很大了。

OSGi 是一個龐大的話題,你只須要知道,有這麼一個複雜的東西,實現了模塊化,每一個模塊能夠獨立安裝、啓動、中止、卸載,就能夠了。

SPI

Java 中有一個 SPI 機制,全稱是 Service Provider Interface,是 Java 提供的一套用來被第三方實現或者擴展的 API,它能夠用來啓用框架擴展和替換組件。

後面會再專門針對這個寫一篇文章,這裏就不細說了。

推薦閱讀:

《系統架構:複雜系統的產品設計與開發》.pdf

《一線架構師實踐指南》.pdf

《按部就班Linux (第2版)》.pdf

關注公衆號「Java後端技術全棧」

免費獲取500G最新學習資料

相關文章
相關標籤/搜索