Java類的加載過程


一個Java類從編碼到最終完成執行,主要包括兩個過程,編譯、運行。

編譯:將我們寫好的.java文件通過Javac命令編譯成.class文件。

運行:把編譯生成的.class文件交由JVM執行。

Jvm運行class類的時候,並不是一次性將所有的類都加載到,內存中,而是用到哪個就加載哪個,並且只加載一次。

 

類的生命周期:加載、鏈接(驗證、准備、解析)、初始化、使用、卸載。

 

 

加載:

加載是類加載過程的一個階段,這兩個概念一定不要混淆。在加載階段, 虛擬機需要完成以下三件事情:

 

(1)Java虛擬機將.class文件讀入內存,並為之創建一個Class對象。

(2)任何類被使用時系統都會為其創建一個且僅有一個Class對象。

(3)這個Class對象描述了這個類創建出來的對象的所有信息,比如有哪些構造方法,都有哪些成員方法,都有哪些成員變量等。

鏈接:

(1)驗證:確保.CLass文件字節流中包含的信息符合當前虛擬機的要求。不會危害的自身安全。Eg:這個類的父類是否繼承了不允許被繼承的類(被finaI修飾的類)

(2)准備:准備階段正式為類變量分配內存以及為類變量設置初始值。注意:這時分配內存的僅僅包括類變量(被static修飾的變量)不包括實例變量,實例變量會再對象實例初始化時分配內存。這里所說的初始值“通常情況”下是數據類型的零值。

(3)解析:將方法區中的字符引用轉換成直接引用

初始化:

對靜態的變量(static)進行賦值,包括初始化代碼塊。

 

類的加載時機:

  1. 創建類的實例也就是new一個對象。
  2. 訪問某個類的或接口的靜態變量 或者給靜態變量賦值、
  3. 調用某個類的靜態方法
  4. 反射(Class.forName(“com.sgd.test”))
  5. 初始化一個類的子類(首先會初始化父類)
  6. JVM啟動時標明的啟動類,即文件名和類名相同的那個類  

 

除此之外,下面幾種情形需要特別指出:

   對於一個final類型的靜態變量,如果該變量的值在編譯時就可以確定下來,那么這個變量相當於“宏變量”。Java編譯器會在編譯時直接把這個變量出現的地方替換成它的值,因此即使程序使用該靜態變量,也不會導致該類的初始化。反之,如果final類型的靜態Field的值不能在編譯時確定下來,則必須等到運行時才可以確定該變量的值,如果通過該類來訪問它的靜態變量,則會導致該類被初始化。

類成員的加載順序圖

 

 

 

 類加載器

類加載器負責加載所有的類,其為所有被載入內存中的類生成一個java.lang.Class實例對象。一旦一個類被加載如JVM中,同一個類就不會被再次載入了。正如一個對象有一個唯一的標識一樣,一個載入JVM的類也有一個唯一的標識。在Java中,一個類用其全限定類名(包括包名和類名)作為標識;但在JVM中,一個類用其全限定類名和其類加載器作為其唯一標識。例如,如果在pg的包中有一個名為Person的類,被類加載器ClassLoader的實例kl負責加載,則該Person類對應的Class對象在JVM中表示為(Person.pg.kl)。這意味着兩個類加載器加載的同名類:(Person.pg.kl)和(Person.pg.kl2)是不同的、它們所加載的類也是完全不同、互不兼容的。

  JVM預定義有三種類加載器,當一個 JVM啟動的時候,Java開始使用如下三種類加載器:

 

 1)根類加載器(bootstrap class loader):它用來加載 Java 的核心類,是用原生代碼來實現的,並不繼承自 java.lang.ClassLoader(負責加載$JAVA_HOME中jre/lib/rt.jar里所有的class,由C++實現,不是ClassLoader子類)。由於引導類加載器涉及到虛擬機本地實現細節,開發者無法直接獲取到啟動類加載器的引用,所以不允許直接通過引用進行操作。

 

下面程序可以獲得根類加載器所加載的核心類庫,並會看到本機安裝的Java環境變量指定的jdk中提供的核心jar包路徑:

 

public class ClassLoaderTest {

 

public static void main(String[] args) {

 

URL[] urls = sun.misc.Launcher.getBootstrapClassPath().getURLs();

for(URL url : urls){

System.out.println(url.toExternalForm());

}

}

}

運行結果:

 

 

 

 

 

  2)擴展類加載器(extensions class loader):它負責加載JRE的擴展目錄,lib/ext或者由java.ext.dirs系統屬性指定的目錄中的JAR包的類。由Java語言實現,父類加載器為null。

 

  3)系統類加載器(system class loader):被稱為系統(也稱為應用)類加載器,它負責在JVM啟動時加載來自Java命令的-classpath選項、java.class.path系統屬性,或者CLASSPATH換將變量所指定的JAR包和類路徑。程序可以通過ClassLoader的靜態方法getSystemClassLoader()來獲取系統類加載器。如果沒有特別指定,則用戶自定義的類加載器都以此類加載器作為父加載器。由Java語言實現,父類加載器為ExtClassLoader。

 

類加載器加載Class大致要經過如下8個步驟:

 

檢測此Class是否載入過,即在緩沖區中是否有此Class,如果有直接進入第8步,否則進入第2步。

如果沒有父類加載器,則要么Parent是根類加載器,要么本身就是根類加載器,則跳到第4步,如果父類加載器存在,則進入第3步。

請求使用父類加載器去載入目標類,如果載入成功則跳至第8步,否則接着執行第5步。

請求使用根類加載器去載入目標類,如果載入成功則跳至第8步,否則跳至第7步。

當前類加載器嘗試尋找Class文件,如果找到則執行第6步,如果找不到則執行第7步。

從文件中載入Class,成功后跳至第8步。

拋出ClassNotFountException異常。

返回對應的java.lang.Class對象。

 

類加載機制:

1.JVM的類加載機制主要有如下3種。

全盤負責:所謂全盤負責,就是當一個類加載器負責加載某個Class時,該Class所依賴和引用其他Class也將由該類加載器負責載入,除非顯示使用另外一個類加載器來載入。

雙親委派:所謂的雙親委派,則是先讓父類加載器試圖加載該Class,只有在父類加載器無法加載該類時才嘗試從自己的類路徑中加載該類。通俗的講,就是某個特定的類加載器在接到加載類的請求時,首先將加載任務委托給父加載器,依次遞歸,如果父加載器可以完成類加載任務,就成功返回;只有父加載器無法完成此加載任務時,才自己去加載。

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

雙親委派機制

這里說明一下雙親委派機制:

 

 

 

 

 

       雙親委派機制,其工作原理的是,如果一個類加載器收到了類加載請求,它並不會自己先去加載,而是把這個請求委托給父類的加載器去執行,如果父類加載器還存在其父類加載器,則進一步向上委托,依次遞歸,請求最終將到達頂層的啟動類加載器,如果父類加載器可以完成類加載任務,就成功返回,倘若父類加載器無法完成此加載任務,子加載器才會嘗試自己去加載,這就是雙親委派模式,即每個兒子都很懶,每次有活就丟給父親去干,直到父親說這件事我也干不了時,兒子自己才想辦法去完成。

 

      雙親委派機制的優勢:采用雙親委派模式的是好處是Java類隨着它的類加載器一起具備了一種帶有優先級的層次關系,通過這種層級關可以避免類的重復加載,當父親已經加載了該類時,就沒有必要子ClassLoader再加載一次。其次是考慮到安全因素,java核心api中定義類型不會被隨意替換,假設通過網絡傳遞一個名為java.lang.Integer的類,通過雙親委托模式傳遞到啟動類加載器,而啟動類加載器在核心Java API發現這個名字的類,發現該類已被加載,並不會重新加載網絡傳遞的過來的java.lang.Integer,而直接返回已加載過的Integer.class,這樣便可以防止核心API庫被隨意篡改。

 

 

 

 

常見問題:自定義一個java.lang.Integer會不會加載?答案:不會,因為按照雙親委派機制,先委托根類和擴展類去加載類。所以會先加載jdk包下的Integer類。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM