jvm之java類加載機制和類加載器(ClassLoader),方法區結構,堆中實例對象結構的詳解


一.類加載或類初始化當程序主動使用某個類時,如果該類還未被加載到內存中,則JVM會通過加載、連接、初始化3個步驟來對該類進行初始化。如果沒有意外,JVM將會連續完成3個步驟。

二.類加載時機: 

 1.創建類的實例,也就是new一個對象

2.訪問某個類或接口的靜態變量,或者對該靜態變量賦值

3.調用類的靜態方法

4.反射(Class.forName("com.lyj.load"))

5.初始化一個類的子類(會首先初始化子類的父類)

6.JVM啟動時標明的啟動類,即文件名和類名相同的那個類    

 

 

三.類加載詳細過程:

1.加載:

     加載指的是將類的class文件讀入jvm的方法區,並創建一個java.lang.class對象,表示這個類的運行時元數據(相當於Java層的Class對象)。

    1)方法區類的class文件詳解:

     classLoader是如何加載class文件和存儲文件信息的:

     當一個classLoder啟動的時候,classLoader的生存地點在jvm中的堆,然后它會去主機硬盤上將A.class裝載到jvm的方法區,方法區中的這個字節文件會被虛擬機拿來new A字節碼(),然后在堆內存生成了一個A字節碼的對象,然后A字節碼這個內存文件有兩個引用一個指向A的class對象,一個指向加載自己的classLoader。那么方法區中的字節碼內存塊,除了記錄一個class自己的class對象引用和一個加載自己的ClassLoader引用之外,還記錄了什么信息呢??見下圖:

 

 

 

      執行new A()的時候,JVM 做了什么工作:

            

    首先,如果這個類沒有被加載過,JVM就會進行類的加載,並在JVM內部創建一個instanceKlass對象表示這個類的運行時元數據(相當於Java層的Class對象)。初始化對象的時候(執行invokespecial A::),JVM就會創建一個instanceOopDesc對象表示這個對象的實例,然后進行Mark Word的填充,將元數據指針指向Klass對象,並填充實例變量。

              元數據—— instanceKlass 對象會存在元空間(方法區),

             對象實例—— instanceOopDesc 會存在Java堆。Java虛擬機棧中會存有這個對象實例的引用。

 

      2)堆中實例化對象的結構:

           在hotSpot虛擬機中,對象在內存中存儲的布局可以分為3塊區域:對象頭(Header)、實例數據(Instance Data)和對齊填充(Padding)。下圖是普通對象實例與數組對象實例的數據結構:

 

 對象頭
  HotSpot虛擬機的對象頭包括兩部分信息:

markword
  第一部分markword,用於存儲對象自身的運行時數據,如哈希碼(HashCode)、GC分代年齡、鎖狀態標志、線程持有的鎖、偏向線程ID、偏向時間戳等,這部分數據的長度在32位和64位的虛擬機(未開啟壓縮指針)中分別為32bit和64bit,官方稱它為“MarkWord”。

klass
  對象頭的另外一部分是klass類型指針,即對象指向它的類元數據的指針,虛擬機通過這個指針來確定這個對象是哪個類的實例.
數組長度(只有數組對象有)
如果對象是一個數組, 那在對象頭中還必須有一塊數據用於記錄數組長度.
 

實例數據
  實例數據部分是對象真正存儲的有效信息,也是在程序代碼中所定義的各種類型的字段內容。無論是從父類繼承下來的,還是在子類中定義的,都需要記錄起來。

 

對齊填充
  第三部分對齊填充並不是必然存在的,也沒有特別的含義,它僅僅起着占位符的作用。由於HotSpot VM的自動內存管理系統要求對象起始地址必須是8字節的整數倍,換句話說,就是對象的大小必須是8字節的整數倍。而對象頭部分正好是8字節的倍數(1倍或者2倍),因此,當對象實例數據部分沒有對齊時,就需要通過對齊填充來補全。

 

 

 

 

2.鏈接

     1)驗證:驗證階段用於檢驗被加載的類是否有正確的內部結構,並和其他類協調一致。

     2)准備:類准備階段負責為類的靜態變量分配內存,並設置默認初始值。

  3)解析:將類的二進制數據中的符號引用替換成直接引用。

3.初始化:初始化是為類的靜態變量賦予正確的初始值。

        如果類中有語句:private static int a = 10,它的執行過程是這樣的,首先字節碼文件被加載到內存后,先進行鏈接的驗證這一步驟,驗證通過后准備階段,給a分配內存,因為變量a是static的,所以此時a等於int類型的默認初始值0,即a=0,然后到解析(后面在說),到初始化這一步驟時,才把a的真正的值10賦給a,此時a=10。

三、類加載器


    類加載器負責加載所有的類,其為所有被載入內存中的類生成一個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子類)。由於引導類加載器涉及到虛擬機本地實現細節,開發者無法直接獲取到啟動類加載器的引用,所以不允許直接通過引用進行操作。

  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個步驟:

1.檢測此Class是否載入過,即在緩沖區中是否有此Class,如果有直接進入第8步,否則進入第2步。
2.如果沒有父類加載器,則要么Parent是根類加載器,要么本身就是根類加載器,則跳到第4步,如果父類加載器存在,則進入第3步。
3.請求使用父類加載器去載入目標類,如果載入成功則跳至第8步,否則接着執行第5步。
4.請求使用根類加載器去載入目標類,如果載入成功則跳至第8步,否則跳至第7步。
5.當前類加載器嘗試尋找Class文件,如果找到則執行第6步,如果找不到則執行第7步。
6.從文件中載入Class,成功后跳至第8步。
7.拋出ClassNotFountException異常。
8.返回對應的java.lang.Class對象。

 

四、類加載機制:

 

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

全盤負責:所謂全盤負責,就是當一個類加載器負責加載某個Class時,該Class所依賴和引用其他Class也將由該類加載器負責載入,除非顯示使用另外一個類加載器來載入。
雙親委派:所謂的雙親委派,則是先讓父類加載器試圖加載該Class,只有在父類加載器無法加載該類時才嘗試從自己的類路徑中加載該類。通俗的講,就是某個特定的類加載器在接到加載類的請求時,首先將加載任務委托給父加載器,依次遞歸,如果父加載器可以完成類加載任務,就成功返回;只有父加載器無法完成此加載任務時,才自己去加載。
緩存機制。緩存機制將會保證所有加載過的Class都會被緩存,當程序中需要使用某個Class時,類加載器先從緩存區中搜尋該Class,只有當緩存區中不存在該Class對象時,系統才會讀取該類對應的二進制數據,並將其轉換成Class對象,存入緩沖區中。這就是為很么修改了Class后,必須重新啟動JVM,程序所做的修改才會生效的原因。

 

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

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

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

 


免責聲明!

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



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