1. 程式人生 > >jvm入門及理解(二)——類載入器子系統

jvm入門及理解(二)——類載入器子系統

一、類載入子系統的作用

  • 類載入子系統負責從檔案系統或者網路中載入Class檔案,class檔案在檔案開頭有特定的檔案標識;
  • ClassLoader只負責class檔案的載入,至於它是否可以執行,則由Execution Engine決定
  • 載入的類資訊存放於一塊成為方法區的記憶體空間。除了類資訊之外,方法區還會存放執行時常量池資訊,可能還包括字串字面量和數字常量(這部分常量資訊是Class檔案中常量池部分的記憶體對映)

二、類載入子系統的概覽圖

 

 類載入子系統主要包括三個環節:

  1. loading
  2. linking:verify,prepare,resolve
  3. initialization

 三、類載入子系統各個環節解析:

  1.Loading(載入)環節

  • 通過一個類的全限定明獲取定義此類的二進位制位元組流;
  • 將這個位元組流所代表的的靜態儲存結構轉化為方法區的執行時資料;
  • 在記憶體中生成一個代表這個類的java.lang.Class物件,作為方法區這個類的各種資料的訪問入口

  

  2.Linking(連結)環節

  • Verify(驗證)

              (1)目的在於確保Class檔案的位元組流中包含資訊符合當前虛擬機器要求,保證被載入類的正確性,不會危害虛擬機器自身安全。

              (2)主要包括四種驗證,檔案格式驗證,源資料驗證,位元組碼驗證,符號引用驗證。

  • Prepare(準備)環節

    (1)為類變數分配記憶體並且設定該類變數的預設初始值,即零值;

    (2)這裡不包含用final修飾的static,因為final在編譯的時候就會分配了,準備階段會顯式初始化;

    (3)不會為例項變數分配初始化,類變數會分配在方法去中,而例項變數是會隨著物件一起分配到java堆中。

  • Resolve(解析)

    (1)將常量池內的符號引用轉換為直接引用的過程。

    (2)事實上,解析操作往往會伴隨著jvm在執行完初始化之後再執行。

    (3)符號引用就是一組符號來描述所引用的目標。符號應用的字面量形式明確定義在《java虛擬機器規範》的class檔案格式中。直接引用就是直接指向目標的指標、相對偏移量或一個間接定位到目標的控制代碼。

    (4)解析動作主要針對類或介面、欄位、類方法、介面方法、方法型別等。對應常量池中的CONSTANT_Class_info/CONSTANT_Fieldref_info、CONSTANT_Methodref_info等。

 

  3.Initialization(初始化)環節

  • 初始化階段就是執行類構造器方法clinit()的過程。
  • 此方法不需要定義,是javac編譯器自動收集類中的所有類變數的賦值動作和靜態程式碼塊中的語句合併而來。
  • 構造器方法中指令按語句在原始檔中的出現的順序執行。
  • Clinit不同於類的構造器(構造器是虛擬機器視角下的init())
  • 若該類有父類,jvm會保證子類的clinit()執行前,父類的clinit()已經執行完畢
  • 虛擬機器必須保證一個類的clinit()在多執行緒下被同步載入。

例子:

 

 

Number的值會先在prepare環節賦值為0,然後在initialzation環節先賦值為20,再賦值為10。

 四、類載入器的分類

 

  • VM支援兩種型別的載入器,分別為引導類載入器(BootStrap ClassLoader)和自定義類載入器(User-Defined ClassLoader)
  • 從概念上來講,自定義類載入器一般指的是程式中由開發人員自定義的一類類載入器,但是java虛擬機器規範卻沒有這麼定義,而是將所有派生於抽象類ClassLoader的類載入器都劃分為自定義類載入器。

  自定義類與核心類庫的載入器:

  • 對於使用者自定義類來說:使用系統類載入器AppClassLoader進行載入
  • java核心類庫都是使用引導類載入器BootStrapClassLoader載入的

  虛擬機器自帶的載入器:

  1)         啟動類載入器(引導類載入器,BootStrap ClassLoader)

  • 這個類載入使用C/C++語言實現的,巢狀在JVM內部
  • 它用來載入java的核心庫(JAVA_HOME/jre/lib/rt.jar/resources.jar或sun.boot.class.path路徑下的內容),用於提供JVM自身需要的類
  • 並不繼承自java.lang.ClassLoader,沒有父載入器
  • 載入拓展類和應用程式類載入器,並指定為他們的父載入器
  • 處於安全考慮,BootStrap啟動類載入器只加載包名為java、javax、sun等開頭的類

 

  2)         拓展類載入器(Extension ClassLoader)

  • java語言編寫 ,由sun.misc.Launcher$ExtClassLoader實現。
  • 派生於ClassLoader類
  • 父類載入器為啟動類載入器
  • 從java.ext.dirs系統屬性所指定的目錄中載入類庫,或從JDK的安裝目錄的jre/lib/ext子目錄(擴充套件目錄)下載入類庫。如果使用者建立的JAR放在此目錄下,也會由拓展類載入器自動載入

 

  3)         應用程式類載入器(系統類載入器,AppClassLoader)

  • java語言編寫, 由sun.misc.Launcher$AppClassLoader實現。
  • 派生於ClassLoader類
  • 父類載入器為拓展類載入器
  • 它負責載入環境變數classpath或系統屬性 java.class.path指定路徑下的類庫
  • 該類載入器是程式中預設的類載入器,一般來說,java應用的類都是由它來完成載入
  • 通過ClassLoader.getSystemClassLoader()方法可以獲取到該類載入器

   使用者自定義類載入器的作用:

  • 隔離載入類
  • 修改類載入的方式
  • 拓展載入源
  • 防止原始碼洩漏

  ClassLoader的常用方法及獲取方法

  ClassLoader類,它是一個抽象類,其後所有的類載入器都繼承自ClassLoader(不包括啟動類載入器)

 

 

  獲取ClassLoader的途徑:

 

 

 

 五、雙親委派機制

 

Java虛擬機器對class檔案採用的是按需載入的方式,也就是說當需要使用該類時才會將她的class檔案載入到記憶體生成的class物件。而且載入某個類的class檔案時,java虛擬機器採用的是雙親微拍模式,即把請求交由父類處理,它是一種任務委派 模式

 

 

 

 

雙親委派機制的優勢:

  • 避免類的重複載入
  • 保護程式安全,防止核心API被隨意篡改

六、沙箱安全機制

     自定義String類,但是在載入子弟敬意String類的時候回率先使用引導類載入器載入,而引導類載入器在載入過程中會先載入jdk自帶的檔案(rt.jar包中的java\lang\String.class),報錯資訊說沒有main方法就是因為載入的是rt.jar包中的String類。這樣可以保證對java核心原始碼的保護,這就是沙箱安全機制。

 

注意:

在jvm中表示兩個class物件是否為同一個類存在的兩個必要條件:

  • 類的完整類名必須一致,包括包名
  • 載入這個類的ClassLoader(指ClassLoader例項物件)必須相同。換句話說,在jvm中,即使這兩個類物件(class物件)來源同一個Class檔案,被同一個虛擬機器所載入,但只要載入它們的ClassLoader例項物件不同,那麼這兩個類物件也是不相等的.
  • JVM必須知道一個型別是有啟動類載入器載入的還是由使用者類載入器載入的。如果一個型別由使用者類載入器載入的,那麼jvm會將這個類載入器的一個引用作為型別資訊的會議部分儲存在方法區中。當解析一個型別到另一個型別的引用的時候,JVM需要保證兩個型別的載入器是相同的。

 

七、類的主動使用和被動使用

主動使用,分為七種情況:

  • 建立類的例項
  • 訪問某各類或介面的靜態變數,或者對靜態變數賦值
  • 呼叫類的靜態方法
  • 反射 比如Class.forName(com.dsh.jvm.xxx)
  • 初始化一個類的子類
  • java虛擬機器啟動時被標明為啟動類的類
  • JDK 7 開始提供的動態語言支援:
  • java.lang.invoke.MethodHandle例項的解析結果REF_getStatic、REF_putStatic、REF_invokeStatic控制代碼對應的類沒有初始化,則初始化

 

除了以上七種情況,其他使用java類的方式都被看作是對類的被動使用,都不會導致類的初始化。

 

&nbs