① 負責從文件系統或者網絡中加載.class文件,Class 文件在文件開頭有特定的文件標識。java
② ClassLoader只負責Class 文件的加載,至於它是否能夠運行,則由Execution Engine決定。bootstrap
數組
④ 從Class文件-->JVM-->最終成爲元數據模板,此過程就要一個運輸工具(類裝載器Class Loader),扮演一個快遞員的角色。安全
JVM 的類加載分爲 5 個階段:加載、驗證、準備、解析、初始化。在類初始化完成後就可使用該類的信息了,當這個類再也不被須要時能夠從 JVM 中卸載。網絡
類加載過程數據結構
JVM 讀取 Class 文件,經過一個類的全限定名獲取定義此類的二進制字節流,將這個字節流所表明的靜態存儲結構轉化爲方法區的運行時數據結構。多線程
根據 Class 文件的描述在堆中建立一個表明這個類的 java.lang.Class對象,做爲方法區這個類的各類數據的訪問入口。工具
在讀取 Class 文件時既能夠經過文件的形式讀取,也能夠經過 jar 包、war 包讀取,還能夠經過代理自動生成 Class或其餘方式讀取。ui
主要用於確保 Class 文件的字節流中包含的信息符合當前虛擬機的要求,保證被加載類的正確性,進一步保障虛擬機自身的安全,只有經過驗證的 Class 文件才能被 JVM 加載。spa
主要包括四種驗證:文件格式驗證,元數據驗證,字節碼驗證,符號引用驗證。
主要工做是在方法區中爲類變量分配內存空間並設置類中變量的初始值。初始值指不一樣數據類型的默認值,這裏須要注意 final 類型的變量和非 final 類型的變量在準備階段的數據初始化過程不一樣。好比一個成員變量的定義以下:
public static int value = 1500;
在上述代碼中,靜態變量 value 在準備階段的初始值是 0,將 value 設置爲 1500 的動做是在對象初始化時完成的,由於 JVM 在編譯階段會將靜態變量的初始化操做定義在構造器中。可是,若是將變量 value 聲明爲 final 類型:
public static final int value = 1500;
則 JVM 在編譯階段後會爲 final 類型的變量 value 生成其對應的 ConstantValue 屬性,虛擬機在準備階段會根據 ConstantValue 屬性將 value 賦值爲 1500。
JVM 將常量池內的符號引用轉換爲直接引用的過程。事實上,解析操做每每會伴隨着JVM在執行完初始化以後再執行。
符號引用就是一組符號來描述所引用的目標。符號引用的字面量形式明肯定義在《java虛擬機規範》的class文件格式中。
直接引用就是直接指向目標的指針、相對偏移量或一個間接定位到目標的句柄。
解析動做主要針對類或接口、字段、類方法、接口方法、方法類型等。對應常量池中的CONSTANT Class info、CONSTANT Fieldref info、CONSTANT Methodref info等。
主要經過執行類構造器的<clinit>方法爲類進行初始化。<clinit>方法是在編譯階段由編譯器自動收集類中靜態語句塊和變量的賦值操做組成的。在準備階段,類中靜態成員變量已經完成了默認初始化,而在初始化階段,<clinit>方法將對靜態成員變量進行顯示初始化。
注意:
1. JVM 規定,只有在父類的<clinit>方法都執行成功後,子類中的<clinit>方法才能夠被執行。所以,JVM中第一個被執行<clinit>方法的類確定是java.lang.Object。
2. 在一個類中既沒有靜態變量賦值操做也沒有靜態語句塊時,編譯器不會爲該類生成<clinit>方法。
3. 靜態代碼塊只能訪問到出如今靜態代碼塊以前的變量,定義在它以後的變量,在前面的靜態語句塊能夠賦值,可是不能訪問。
4. 接口也須要經過<clinit>方法爲接口中定義的靜態成員變量顯示初始化。
5. 接口中不能使用靜態代碼塊,但仍然有變量初始化的賦值操做,由於接口與類同樣都會生成<clinit>方法。不一樣的是,執行接口的<clinit>方法不須要先執行父接口的<clinit>方法,只有當父接口中的靜態成員變量被使用到時纔會執行父接口的<clinit>方法。
6. 虛擬機會保證在多線程環境中一個類的<clinit>方法被正確地加鎖同步。當多條線程同時去初始化一個類時,只會有一個線程去執行該類的<clinit>方法,其它線程都被阻塞等待,直到活動線程執行<clinit>方法完畢。
★ 常量在編譯時會將其常量值存入使用該常量的類的常量池中,該過程不須要調用常量所在的類,所以不會觸發該常量類的初始化。
★ 在子類引用父類的靜態字段時,不會觸發子類的初始化,只會觸發父類的初始化。
★ 定義對象數組,不會觸發該類的初始化。
★ 在使用類名獲取 Class 對象時不會觸發類的初始化。
★ 在使用 Class.forName 加載指定的類時,能夠經過 initialize 參數設置是否須要對類進行初始化。
★ 在使用 ClassLoader 默認的 loadClass 方法加載類時不會觸發該類的初始化。
JVM 提供了 3 種類加載器,分別是啓動類加載器、擴展類加載器和應用程序類加載器,還有一種是用戶自定義類加載器。
如圖所示:
JVM 類加載器
在Java的平常應用程序開發中,類的加載幾乎是由上述3種類加載器相互配合執行的,但在必要時,咱們還須要自定義類加載器,來定製類的加載方式。咱們能夠經過繼承 java.lang.ClassLoader 實現自定義的類加載器。
✔ 隔離加載類
✔ 修改類加載的方式
✔ 擴展加載源
✔ 防止源碼泄漏
JVM 支持兩種類型的類加載器。分別爲引導類加載器(Bootstrap ClassLoader)和自定義類加載器(User-Defined ClassLoader)。
規定全部派生於抽象類 ClassLoader的類加載器都被劃分爲自定義類加載器,ClassLoader是一個抽象類,其後全部的類加載器都繼承自ClassLoader(不包括啓動類加載器),因此啓動類加載器爲一類,其他的類加載器爲另外一類。
咱們看一段代碼,獲取它們的類加載器:
public class ClassLoaderTest { public static void main(String[] args) { // 1.獲取:系統類加載器
ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader(); System.out.println(systemClassLoader); // 2.獲取其上層的:擴展類加載器
ClassLoader extClassLoader = systemClassLoader.getParent(); System.out.println(extClassLoader); // 3.試圖獲取:根加載器(啓動類加載器)
ClassLoader bootstrapClassLoader = extClassLoader.getParent(); System.out.println(bootstrapClassLoader); // 4.獲取:自定義加載器
ClassLoader classLoader = ClassLoaderTest.class.getClassLoader(); System.out.println(classLoader); // 5.獲取:String類型的加載器
ClassLoader classLoader1 = String.class.getClassLoader(); System.out.println(classLoader1); } }
運行結果:
咱們能夠看到,目前用戶代碼所使用的加載器爲系統類加載器,其上是擴展類加載器,兩者是同屬一類加載,均可以用代碼直接獲取。可是,根加載器(啓動類加載器)沒法直接經過代碼獲取。同時,咱們經過獲取String類型的加載器,發現是null,那麼說明String類型是經過根加載器進行加載的,也就是說Java的核心類庫都是使用根加載器進行加載的。
Java虛擬機對class文件採用的是按需加載的方式,也就是說當須要使用該類時纔會將它的class文件加載到內存生成class對象。並且加載某個類的class文件時,Java虛擬機採用的是雙親委派模式,即把請求交由父類處理,它是一種任務委派模式。
雙親委派機制指一個類在收到類加載請求後不會嘗試本身加載這個類,而是把該類加載請求向上委派給其父類去完成,其父類在接收到該類加載請求後又會將其委派給本身的父類,以此類推,這樣全部的類加載請求都被向上委派到啓動類加載器中。若父類加載器在接收到類加載請求後發現本身也沒法加載該類(一般緣由是該類的 Class 文件在父類的類加載路徑中不存在),則父類會將該信息反饋給子類並向下委派子類加載器加載該類,直到該類被成功加載,若找不到該類,則 JVM 會拋出 ClassNotFoud 異常。
雙親委派過程
自定義String類,但在加載自定義String類的時候會率先使用引導類加載器加載,而引導類加載器在加載的過程當中會先加載jdk自帶的文件(rt.jar包中java\lang\String.class),報錯信息說沒有main方法,就是由於加載的是rt.jar包中的string類,並非自定義的String類。這樣能夠保證對java核心源代碼的保護,這就是沙箱安全機制。
★ 避免類的重複加載
★ 保護程序安全,防止核心API被隨意篡改
☆ 自定義類:java.lang.String(報錯:阻止建立 java.lang開頭的類)
☆ 自定義類:java.lang.ShkStart(報錯:阻止建立 java.lang開頭的類)
在JVM中表示兩個class對象是否爲同一個類存在兩個必要條件:
☛ 類的完整類名必須一致,包括包名。
☛ 加載這個類的ClassLoader(指ClassLoader實例對象)必須相同。
換句話說,在JVM中,即便兩個類對象(Class對象)來源同一個Class文件,被同一個虛擬機所加載,但只要加載它們的ClassLoader實例對象不一樣,那麼這兩個類對象就是不相等的。
JVM必須知道一個類是由啓動加載器加載的仍是由用戶類加載器加載的。若是一個類是由用戶類加載器加載的,那麼JVM會將這個類加載器的一個引用做爲類型信息的一部分保存在方法區中,當解析一個類到另外一個類的引用時,JVM須要保證這兩個類的類加載器是相同的。
Java程序對類的使用方式分爲:主動使用和被動使用。
主動使用,有七種狀況:
建立類的實例
訪問某個類或接口的靜態變量,或者對該靜態變量賦值
調用類的靜態方法
反射(好比:Class.forName("com.atguigu.Test"))
初始化一個類的子類
Java虛擬機啓動時被標明爲啓動類的類
JDK7開始提供的動態語言支持:java.lang.invoke.MethodHandle實例的解析結果REF_getStatic、REF_putStatic、REF_invokeStatic句柄對應的類沒有初始化,則初始化
除了以上七種狀況,其餘使用Java類的方式都被看做是對類的被動使用,都不會致使類的初始化。