1. 程式人生 > >JVM類加載

JVM類加載

jvm

一、類加載器

1、什麽是類加載器

類的加載指的是將類的.class文件中的二進制數據讀入到內存中,將其放在運行時數據區的方法區內,然後在堆區創建一個java.lang.Class對象,用來封裝類在方法區內的數據結構。完成類加載的家夥就是類加載器。

2、都有哪些類加載器

技術分享

                    ClassLoader loader=Test01.         (loader!=              loader=loader.getParent();    }

結果:

  sun.misc.Launcher$AppClassLoader
  sun.misc.Launcher$ExtClassLoader
  null

技術分享

由輸出結果可以看出ExtClassLoader是AppClassLoader的父類,而ExtClassLoader的父類卻是null,原因是ExtClassLoader的父類加載器是BootStrap,BootStrap是JVM最底層的引導類加載器用C語言編寫的,所以找不到一個確定的返回父Loader的方式,於是就返回null。

為什麽要有這個引導類加載器:

  類加載器也是java類,他們也需要類加載器加載進入內存,顯然必須要有第一個不是java類的類加載器,來完成這個工作,這個正是BootStrap。

JVM中類加載器的結構:

技術分享

3、各個類加載器的作用

BootStrap ClassLoader(啟動類加載器):負責加載存放在D:\Program Files (x86)\Java\jdk1.7.0_79\jre\lib下,或被-Xbootclasspath參數指定的路徑中的,並且能被虛擬機識別的類庫(如rt.jar,所有的java.*開頭的類均被Bootstrap ClassLoader加載)。啟動類加載器是無法被Java程序直接引用的。

Extension ClassLoader(擴展類加載器):該加載器由sun.misc.Launcher$ExtClassLoader實現,它負責加載D:\Program Files (x86)\Java\jdk1.7.0_79\jre\lib\ext目錄中,或者由java.ext.dirs系統變量指定的路徑中的所有類庫(如javax.*開頭的類),開發者可以直接使用擴展類加載器。

Application ClassLoader(應用程序類加載器):該類加載器由sun.misc.Launcher$AppClassLoader來實現,它負責加載用戶類路徑(ClassPath)所指定的類,開發者可以直接使用該類加載器,如果應用程序中沒有自定義過自己的類加載器,一般情況下這個就是程序中默認的類加載器。

4、JVM類加載機制(JVM需要加載一個類時,到底會派出哪個類加載器去執行?)

全盤負責,當前線程的類加載器負責加載某個Class時,該Class所依賴的和引用的其他Class也將由該類加載器負責載入,除非顯示使用CLassLoader.loadClass()指定類加載器來載入

父類委托,先讓父類加載器試圖加載該類,只有在父類加載器無法加載該類時才嘗試從自己的類路徑中加載該類。所以我們在開發中盡量不要使用與JDK相同的類(例如自定義一個java.lang.System類),因為父類加載器中已經有一份java.lang.System類了,它會直接將該類給程序使用,而你自定義的類壓根就不會被加載。

技術分享

123、如果BootStrap ClassLoader加載失敗(例如在$JAVA_HOME/jre/4、若ExtClassLoader也加載失敗,則會使用AppClassLoader來加載,如果AppClassLoader也加載失敗,則會報出異常ClassNotFoundException。

雙親委派模型意義:

  -系統類防止內存中出現多份同樣的字節碼

  -保證Java程序安全穩定運行

技術分享

緩存機制,緩存機制將會保證所有加載過的Class都會被緩存,當程序中需要使用某個Class時,類加載器先從緩存區尋找該Class,只有緩存區不存在,系統才會讀取該類對應的二進制數據,並將其轉換成Class對象,存入緩存區。這就是為什麽修改了Class後,必須重啟JVM,程序的修改才會生效。

5、自定義類加載器

二、類的加載

1、類的加載:

  類的加載指的是將類的.class文件中的二進制數據讀入到內存中,將其放在運行時數據區的方法區內,然後在堆區創建一個java.lang.Class對象,用來封裝類在方法區內的數據結構。類的加載的最終產品是位於堆區中的Class對象,Class對象封裝了類在方法區內的數據結構,並且向Java程序員提供了訪問方法區內的數據結構的接口。

技術分享

字節碼(.class)文件來源:

– 從本地系統中直接加載
– 通過網絡下載.class文件
– 從zip,jar等歸檔文件中加載.class文件
– 從專有數據庫中提取.class文件
– 將Java源文件動態編譯為.class文件

2、類加載的過程

JVM將javac編譯好的class文件加載到內存中,並對該數據進行驗證、解析和初始化,最終形成JVM可以直接使用的JAVA類型的過程。

技術分享

(1)、加載:加載階段其實就是JVM通過一個類的全限定名來獲取其定義的二進制字節流,並將這個字節流所代表的靜態存儲結構轉化為方法區的運行時數據結構且在Java堆中生成一個代表這個類的java.lang.Class對象,作為對方法區中這些數據的訪問入口。在該階段我們開發人員可以幹預,例如:我們可以指定類加載器來加載該字節數組或者自定義類加載器來加載。

(2)、鏈接:將java類的二進制代碼合並到JVM的運行狀態中的過程

a、驗證:驗證是為了確保Class文件的字節流中包含的信息符合當前虛擬機的要求,並且不會危害虛擬機自身的安全。

b、準備:該階段是在方法區中為類變量(static變量)分配內存並設置類變量初始值。例如:public static int flag=1;該階段初始化值為0。

c、解析:虛擬機將常量池中的符號引用替換為直接引用的過程。(直接引用就是直接指向目標的指針、相對偏移量或一個間接定位到目標的句柄)

(3)、初始化:初始化為類的靜態變量賦予正確的初始值,JVM負責對類進行初始化,主要對類變量進行初始化。

  • 初始化階段就是執行類構造器<clinit>()的過程,類構造器<clinit>()是由編譯器自動收集類中的所有類變量的賦值動作和靜態語句塊中的語句合並產生的。

  • 當初始化一個類的時候,如果發現其父類還沒有進行過初始化,則需要先初始化其父類。

  • 虛擬機會保證一個類的<clinit>()方法在多線程環境中被正確加鎖和同步。

  • 當訪問一個java 類的靜態域時,只有正真申明這個域的類才會被初始化。

類的初始化時機:

  • 當虛擬機啟動則一定會加載main方法所在的類

技術分享

    width=100"靜態代碼塊"

技術分享

  • 當初始化一個類時,如果其父類未被初始化則一定先初始化其父類

技術分享

   = A "靜態代碼塊A""靜態代碼塊FatherA"

  靜態代碼塊FatherA
  靜態代碼塊A

技術分享

  • new一個對象的時候類被加載

  • 調用類的靜態方法和靜態成員變量(除了final常量 常量在編譯階段就存入調用類的常量池中,所以無需初始化類)

  • 使用java.lang.reflect包中的方法進行反射調用類會被初始化

類的被動引用不會初始化類:

  • 調用final常量不會初始化應為 常量在編譯階段就存入調用類的常量池中,所以無需初始化類。

  • 通過數組定義的類引用不會初始化類

技術分享

   = A[10"靜態代碼塊A"

技術分享

  • 當訪問一個靜態變量時,只有正真聲明這個靜態變量的類才會被初始化(通過子類調用父類的靜態變量,子類不會被初始化)

技術分享

    A "靜態代碼塊A"   width=100"靜態代碼塊FatherA"靜態代碼塊FatherA
 

技術分享

(4)、卸載

Java虛擬機將結束生命周期的時機:

  • 執行了System.exit()方法

  • 程序正常執行結束

  • 程序在執行過程中遇到了異常或錯誤而異常終止

  • 由於操作系統出現錯誤而導致Java虛擬機進程終止


JVM類加載