天天看點

探究JVM——從位元組碼到對象(類加載、ClassLoader、雙親委派機制、方法區類資訊、Class對象)

引言

衆所周知,Java代碼被編譯器處理成位元組碼檔案,然後存放在計算機的硬碟之上,但程式運作時CPU是與記憶體打交道的,這就需要把位元組碼檔案加載到記憶體中去并生成一個個的執行個體對象,此篇文章将剖析此過程,弄清楚安眠于硬碟中位元組碼是如何變成生龍活虎的對象滴。

ClassLoader 類加載器

顧名思意,類加載器的作用就是把Class檔案加載到記憶體,之後生成一個Class對象。class檔案可以來自于本地硬碟、jar包、網絡流甚至是基于動态代理技術運作時計算生成的,隻要符合JVM規範所規定的格式都能被加載。

探究JVM——從位元組碼到對象(類加載、ClassLoader、雙親委派機制、方法區類資訊、Class對象)

類加載器可大緻分為兩類:虛拟機自帶類加載器與使用者自定義的加載器。

虛拟機自帶類加載器

  • Bootstrap ClassLoader

    此加載器為最底層最基礎的加載器,使用C/C++語言實作,嵌套在了JVM内部,用于加載Java的核心庫(JAVA_HOME/jre/lib/rt.jar、resource.jar 或sun.boot.class.path路徑下的内容)與拓展類加載器 Extension ClassLoader 和應用程式類加載器 APPClassLoader,并為他們指定父類加載器,也就是說這個類加載器加載了其他的類加載器,是以它叫啟動加載器。另外出于安全考慮,Bootstrap啟動類加載器隻加載java、javax、sun等開頭的類。

  • Extension ClassLoader 與 APPClassLoader

    這個兩個加載器由Java語言編寫,派生于ClassLoader類。ExtensionClassLoader 負責加載 JVM 擴充類,比如 swing 系列、内置的 js 引擎、xml 解析器 等等,這些庫名通常以 javax 開頭,它們的 jar 包位于 JAVA_HOME/lib/ext/*.jar 中,有很多 jar 包。

    AppClassLoader 是直接面向我們使用者的加載器,它會加載 Classpath 環境變量裡定義的路徑中的 jar 包和目錄。我們自己編寫的代碼以及使用的第三方 jar 包通常都是由它來加載的。

使用者自定義類加載器

大多數情況下JVM所自帶的加載器已經足夠滿足我們的日常需求,但有些情況下比如出于安全性考慮,位元組碼會被加密,防止被人反編譯,此時就需要開發者自定義類加載器解密位元組碼,并将之加載到記憶體之中。

雙親委派模式

JVM對于class檔案采用的是按需加載的方式,也就是說當程式需要使用到某個類時才會将它的class檔案加載到記憶體,生成class對象,加載時采用了雙親委派模式,此名稱看似高大上,實則非常淺顯易懂。即每個類加載器都很“懶”,都會先把請求交由上層處理,若上層不能處理,這個類加載器再自己親自處理。

探究JVM——從位元組碼到對象(類加載、ClassLoader、雙親委派機制、方法區類資訊、Class對象)

這樣做的好處第一避免了類的重複加載,確定class檔案隻會被加載一次。第二點就是保護了程式的安全,防止核心API被随意篡改。比如下方代碼我自己寫了一個String類,包名也為java.lang,當我在其他地方使用String類時,仍然會是系統自帶的那個String類,而不是我自定義的這個。

package java.lang;
public class String {
    public void say(){
        System.out.println(222);
    }
}
           

方法區 類資訊

class檔案加載到記憶體後會放在JVM的記憶體模型中有一個叫做方法區的區域,在jdk 1.8之前叫做永久代,之後叫做元空間,該區域儲存着每個類(類、接口、枚舉)的資訊,包括這個類的字段名稱、字段類型、修飾符 還有方法名稱、方法傳回類型、方法參數、方法修飾符……這些資訊就是一個類的模闆,在調用反射相關的API時,比如

getMethods()

getFields

其底層都會在方法區去尋找相關資訊。

Class對象

首先在Java中有個Class類,在一個運作中的Java程式中,每個Class類的執行個體代表了類與接口。

探究JVM——從位元組碼到對象(類加載、ClassLoader、雙親委派機制、方法區類資訊、Class對象)

當某個.class檔案被加載到記憶體中以後,其并不會變成一個執行個體對象,而是變成這個類的Class對象,也可以了解為這是一個模闆對象,所有的執行個體對象根據這個模闆對象創造而來。

舉個栗子:User.class 被加載的記憶體後變成了

Class<? extends User> userClass

這個對象,某個對象的Class對象可調用其

getClass()

來獲得。

怎麼說呢,這裡所謂的加載,說成解析更為貼切,JVM解析.class檔案,然後在方法區生成類的基本資訊,在堆區生成Class對象。