尚學堂【官網】-西安Java培訓|c++培訓|Android培訓|安卓培訓|java視頻教程|軟件工程師|-西安雁塔尚學堂計算機學校
Java教程  尚學堂首頁Java學院Java教程

JavaJVM類加載機制 西安尚學堂

www.zyopwk.live 發布人:java  |  來自:本站  |  發布時間:2017-11-09 14:31:33  |  點擊次數:1700

一、什么是類加載機制

在JVM系列文章(三):Class文件內容解析中我們了解了Class文件存儲格式的具體細節,在Class文件中描述的各種信息,最終都需要加載到虛擬機中之后才能運行和使用。本文所關注的是虛擬機如何加載Class文件、Class文件中的信息進入到虛擬機之后會發生哪些變化。

虛擬機把描述類的數據從Class文件加載到內存,并對數據進行校驗、轉換解析和初始化,最終形成可以被虛擬機直接使用的Java類型。這就是虛擬機的類加載機制。

在Java中,類型的加載、連接和初始化過程都是在程序運行期間完成的,雖然增加了性能開銷,但是Java里動態拓展的語言特性就是依賴運行期動態加載和動態連接這個提點實現的。甚至可以在運行時從網絡上加載一段二進制流作為程序代碼的一部分。

二、類加載的時機

類從被加載到虛擬機內存中開始,到卸載出內存為止,整個生命周期包括加載、驗證、準備、解析、初始化、使用和卸載。其中驗證、準備、解析統稱為連接。

加載、驗證、準備、初始化和卸載這5個階段的順序是固定的,解析階段則不一定,在某些情況下,解析階段有可能在初始化階段結束后開始,以支持Java的動態綁定。

首先這里我們要明確類加載!=加載。加載只是其中的一個階段。因此我們下面說的是初始化的時機。(有點繞啊這東西)

什么時候進行初始化?有且只有以下5種情況(稱為對一個類進行主動引用):

1.遇到new(使用new關鍵字實例化對象)、getstatic(獲取一個類的靜態字段,final修飾符修飾的靜態字段除外)、putstatic(設置一個類的靜態字段,final修飾符修飾的靜態字段除外)和invokestatic(調用一個類的靜態方法)這4條字節碼指令時,如果類還沒有初始化,則必須首先對其初始化

2.使用java.lang.reflect包中的方法對類進行反射調用時,如果類還沒有初始化,則必須首先對其初始化

3.當初始化一個類時,如果其父類還沒有初始化,則必須首先初始化其父類

4.當虛擬機啟動時,需要指定一個主類(main方法所在的類),虛擬機會首選初始化這個主類

5.使用JDK1.7的動態語言支持時,如果一個java.lang.invoke.MethodHandle實例最后的解析結果為REF_getStatic、REF_putstatic、REF_invokeStatic的方法句柄,并且這個方法句柄所對應的類沒有進行過初始化,則需要先觸發其初始化。

被動引用不會:

1.通過子類調用父類的static字段,只會初始化父類,不會初始化子類。

2.new ClassA[10]不會初始化ClassA

3. ClassA中的final static常量,被ClassB引用,但是不會初始化ClassA,因為常量在編譯階段會存入調用類的常量池中,本質上沒有直接引用到定義常量的類。

三、類加載的過程

1.加載

加載”是“類加載過程”的一個階段,在初始化之前完成(初始化的時機上文已經說了)。在加載階段需要完成下面3件事情:

1)通過一個類的全限定名來獲取定義此類的二進制字節流

2)將這個字節流所代表的靜態存儲結構轉化為方法區的運行時數據結構

3)在內存中生成一個代表這個類的java.lang.Class對象,作為方法區這個類的各種數據的訪問入口

獲取二進制字節流的方式:

  • 從ZIP包中獲取,最終成為日后JAR/EAR/WAR格式的基礎;
  • 從網絡獲取,最典型的應用就是Applet;
  • 運行時計算生成,這種場景使用最多的就是動態代理技術;
  • 由其他文件生成,典型場景是JSP應用,即由JSP文件生成對應的Class類;
  • 從數據庫讀取,相對少見,有些中間件服務器可以選擇把程序安裝到數據庫中來完成程序代碼在集群間的分發;
  • and so on..

2.驗證

驗證是連接階段的第一步,目的是為了確保Class文件的字節流中包含的信息符合當前虛擬機的要求,并且不會危害虛擬機自身的安全。

因為Class文件的來源不一定是編譯產生,甚至可以直接用十六進制編輯器直接編寫。所以如果不檢查輸入的字節流,對其完全信任的話,很可能因為載入有害的字節流而導致系統崩潰。

從整體來看,驗證階段大致完成下面4個階段的檢驗:

1)文件格式驗證

     字節流是否符合Class文件格式的規范,并且能被當前版本的虛擬機處理。比如是否以魔數0xCAFEBABE開頭、主次版本號是否在當前虛擬機處理范圍內、常量池的常量中是否有不被支持的常量類型(檢查常量tag標志)等等等等。該驗證階段的主要目的是保證輸入的字節流能正確地解析并存儲于方法區之內,格式上符合描述一個JAVA類型信息的要求。只有通過這個階段的驗證后,字節流才會進入內存的方法區進行存儲,所以后面的3個驗證階段全部是基于方法區的存儲結構進行的,不會再直接操作字節流。

2)元數據驗證

     這一階段是對于字節碼描述的信息進行語義分析,以保證其描述的信息符合Java語言規范的要求。可能包括的驗證點如下:

     是否有父類;父類是否繼承了不被允許的類(被final修飾的);如果不是抽象類,是否實現了其父類或者接口之中要求實現的所有方法;類中的字段、方法是否與父類產生矛盾(比如覆蓋了父類的final字段或者出現不符合規則的方法重載,例如方法參數都一致但返回值不同等);and so on...

3)字節碼驗證

     進行數據流和控制流分析,即對類的方法體進行校驗分析以保證被校驗類的方法在運行時不會做出危害虛擬機安全的行為(如:保證跳轉指令不會跳轉到方法體以外的字節碼指令上等)

     即使一個方法通過了字節碼驗證,也不能說明其一定是安全的(通過程序去校驗程序邏輯是無法做到絕對準確的)

4)符號引用驗證

     對類自身以外(常量池中的各種符號引用)的信息進行匹配性校驗,通常檢查:符號引用中通過字符串描述的全限定名是否能找到對應的類、指定的類中是否存在符合描述符與簡單名稱描述的方法與字段

3.準備

     準備階段是正式為類變量(被static修飾的變量)分配內存并設置類變量初始值(通常情況下是數據類型的0值,具體賦值階段是初始化階段)的階段,這些變量所使用的內存都將在方法區中進行分配。如果類字段的字段屬性表中包含ConstantValue屬性,那在準備階段變量就會被初始化為ConstantValue屬性所指定的值,即如果a變量定義變為public final static int a = 123;,編譯時javac會為a生成ConstantValue屬性,準備階段虛擬機就會根據ConstantValue的設置將a的值置為123 。

4.解析

解析階段是虛擬機將常量池內的符號引用替換為直接引用的過程。

1)符號引用:

     以一組符號來描述所引用的目標,符號可以是任何形式的字面量,只要使用時可以無歧義的定位到目標即可。符號引用與虛擬機實現的內存布局無關,引用目標并不一定已經加載到內存中

2)直接引用:

     直接指向目標的指針、相對偏移量或一個能間接定位到目標的句柄,直接引用與虛擬機實現的內存布局相關,如果有了直接引用,引用目標必定已經加載到內存中

     虛擬機規范并未規定解析動作發生的具體時間,僅要求在執行anewarray、checkcast、getfield、getstatic、instanceof、invokeinterface、invokespecial、invokestatic、invokevirtual、multianewarray、new、putfield和putstatic這13個用于操作符號引用的字節碼指令之前,先對它們所使用的符號引用進行解析。

下面說的很清楚:

     在java中,一個java類將會編譯成一個class文件。在編譯時,java類并不知道引用類的實際內存地址,因此只能使用符號引用來代替。比如org.simple.People類引用org.simple.Tool類,在編譯時People類并不知道Tool類的實際內存地址,因此只能使用符號org.simple.Tool(假設)來表示Tool類的地址。而在類裝載器裝載People類時,此時可以通過虛擬機獲取Tool類 的實際內存地址,因此便可以既將符號org.simple.Tool替換為Tool類的實際內存地址,及直接引用地址。

5.初始化

類初始化階段是“類加載過程”中最后一步,在之前的階段,除了加載階段用戶應用程序可以通過自定義類加載器參與,其它階段完全由虛擬機主導和控制,直到初始化階段,才真正開始執行類中定義的Java程序代碼

四、類加載器

虛擬機設計團隊把類加載階段中的“通過一個類的全限定名來獲取描述此類的二進制字節流”這個動作放到Java虛擬機外部去實現,以便讓應用程序自己決定如何去獲取所需要的類。實現這個動作的代碼模塊稱為類加載器。

對于任意一個類,都需要由加載它的類加載器和這個類本身一同確立其在Java虛擬機中的唯一性,每一個類加載器都擁有一個獨立的類名稱空間。即比較兩個類是否“相等”,只有在這兩個類是由同一個類加載器加載的前提下才有意義。“相等”包括代表類的Class對象的equals()方法、isAssinableFrom()方法、isInstance()方法的返回結果,也包括使用instanceof關鍵字做對象所屬關系判定等情況。

雙親委派模型

從Java虛擬機角度來講,只存在兩種不同的類加載器:一種是啟動類加載器,是虛擬機自身的一部分;另一種就是所有其他的類加載器,獨立于虛擬機外部,全都繼承自抽象類java.lang.ClassLoader。

從Java開發人員的角度來看,類加載器分為3種:

啟動類加載器(Bootstrap ClassLoader):這個類存放在\lib目錄中,無法被Java程序直接引用,用戶在編寫自定義類加載器時,如果需要把加載請求委派給引導類加載器,那直接使用null代替即可。

拓展類加載器(Extension ClassLoader):這個類加載器負責加載\lib\ext目錄中的所有類庫,開發者可以直接使用。

應用程序類加載器(Application ClassLoader):這個類加載器是ClassLoader中的getSystemClassLoader()方法的返回值,所以一般也稱它為系統類加載器,負責加載用戶類路徑上所指定的類庫。開發者可以直接使用。

雙親委派模型要求除了頂層的啟動類加載器外,其他的類加載器都應當有自己的父類加載器。這里類加載器之間的父子關系一般不會以繼承關系來實現,而是都使用組合關系來復用父加載器的代碼。

工作過程:

如果一個類加載器收到了類加載的請求,它首先不會自己去嘗試加載這個類,而是把這個請求委派給父類加載器去完成,每一個層次的類加載器都是如此,因此所有的加載請求最終都應該傳遞到頂層的啟動類加載器中,只有當父類加載器反饋自己無法完成這個請求(它的搜索范圍中沒有找到所需的類)時,子加載器才會嘗試自己去加載。

好處:

Java類隨著它的類加載器一起具備了一種帶有優先級的層次關系。例如類Object,它放在rt.jar中,無論哪一個類加載器要加載這個類,最終都是委派給啟動類加載器進行加載,因此Object類在程序的各種類加載器環境中都是同一個類。(判斷兩個類是否相同是通過classloader.class這種方式進行的,所以哪怕是同一個class文件如果被兩個classloader加載,那么他們也是不同的類)。

 

當前文:JavaJVM類加載機制 西安尚學堂
上一頁:Java設計模式面試題與答案 西安尚學堂
下一頁:西安Java培訓隊列線程安全操作
在線報名(*為必填項)
云南快乐十分开奖走势图