1. 程式人生 > >JVM類載入器機制與類載入過程

JVM類載入器機制與類載入過程

0、前言

讀完本文,你將瞭解到:

一、為什麼說Jabalpur語言是跨平臺的

二、Java虛擬機器啟動、載入類過程分析

三、類載入器有哪些?其組織結構是怎樣的?

四、雙親載入模型的邏輯和底層程式碼實現是怎樣的?

五、類載入器與Class<T>  例項的關係

六、執行緒上下文載入器

一、為什麼說Java語言是跨平臺的?

Java語言之所以說它是跨平臺的、可以在當前絕大部分的作業系統平臺下執行,是因為Java語言的執行環境是在Java虛擬機器中。 
Java虛擬機器消除了各個平臺之間的差異,只要作業系統平臺下安裝了Java虛擬機器,那麼使用Java開發的東西都能在其上面執行。如下圖所示:

這裡寫圖片描述

         Java虛擬機器對各個平臺而言,實質上是各個平臺上的一個可執行程式。例如在windows平臺下,java虛擬機器對於windows而言,就是一個java.exe程序而已。

二、Java虛擬機器啟動、載入類過程分析

下面我將定義一個非常簡單的java程式並執行它,來逐步分析java虛擬機器啟動的過程。

[java] view plain copy  print?在CODE上檢視程式碼片派生到我的程式碼片
  1. package org.luanlouis.jvm.load;  
  2. import sun.security.pkcs11.P11Util;  
  3. /** 
  4.  * Created by louis on 2016/1/16.
     
  5.  */
  6. publicclass Main{  
  7.     publicstaticvoid main(String[] args) {  
  8.         System.out.println("Hello,World!");  
  9.         ClassLoader loader = P11Util.class.getClassLoader();  
  10.         System.out.println(loader);  
  11.     }  
  12. }  

在windows命令列下輸入: 
java    org.luanlouis.jvm.load.Main
當輸入上述的命令時: 
windows開始執行{JRE_HOME}/bin/java.exe程式,java.exe 程式將完成以下步驟: 
1.  根據JVM記憶體配置要求,為JVM申請特定大小的記憶體空間;

2.  建立一個引導類載入器例項,初步載入系統類到記憶體方法區區域中;

3.   建立JVM 啟動器例項 Launcher,並取得類載入器ClassLoader

4.  使用上述獲取的ClassLoader例項載入我們定義的 org.luanlouis.jvm.load.Main類;

5.  載入完成時候JVM會執行Main類的main方法入口,執行Main類的main方法;

6.  結束,java程式執行結束,JVM銷燬。

Step 1.根據JVM記憶體配置要求,為JVM申請特定大小的記憶體空間


為了不降低本文的理解難度,這裡就不詳細介紹JVM記憶體配置要求的話題,今概括地介紹一下記憶體的功能劃分。

JVM啟動時,按功能劃分,其記憶體應該由以下幾部分組成: 
這裡寫圖片描述 
如上圖所示,JVM記憶體按照功能上的劃分,可以粗略地劃分為方法區(Method Area) 和堆(Heap),而所有的類的定義資訊都會被載入到方法區中。

Step 2. 建立一個引導類載入器例項,初步載入系統類到記憶體方法區區域中;

JVM申請好記憶體空間後,JVM會建立一個引導類載入器(Bootstrap Classloader)例項,引導類載入器是使用C++語言實現的,負責載入JVM虛擬機器執行時所需的基本系統級別的類,如java.lang.String, java.lang.Object等等。

引導類載入器(Bootstrap Classloader)會讀取 {JRE_HOME}/lib 下的jar包和配置,然後將這些系統類載入到方法區內。

本例中,引導類載入器是用 {JRE_HOME}/lib載入類的,不過,你也可以使用引數 -Xbootclasspath 或 系統變數sun.boot.class.path來指定的目錄來載入類。

一般而言,{JRE_HOME}/lib下存放著JVM正常工作所需要的系統類,如下表所示:

檔名 描述
rt.jar 執行環境包,rt即runtime,J2SE 的類定義都在這個包內
charsets.jar 字符集支援包
jce.jar 是一組包,它們提供用於加密、金鑰生成和協商以及 Message Authentication Code(MAC)演算法的框架和實現
jsse.jar 安全套接字拓展包Java(TM) Secure Socket Extension
classlist 該檔案內表示是引導類載入器應該載入的類的清單
net.properties JVM 網路配置資訊

引導類載入器(Bootstrap ClassLoader) 載入系統類後,JVM記憶體會呈現如下格局:
這裡寫圖片描述

  • 引導類載入器將類資訊載入到方法區中,以特定方式組織,對於某一個特定的類而言,在方法區中它應該有 執行時常量池型別資訊欄位資訊方法資訊類載入器的引用對應class例項的引用等資訊。
  • 類載入器的引用,由於這些類是由引導類載入器(Bootstrap Classloader)進行載入的,而 引導類載入器是有C++語言實現的,所以是無法訪問的,故而該引用為NULL
  • 對應class例項的引用, 類載入器在載入類資訊放到方法區中後,會建立一個對應的Class 型別的例項放到堆(Heap)中, 作為開發人員訪問方法區中類定義的入口和切入點。
小測試:
當我們在程式碼中嘗試獲取系統類如java.lang.Object的類載入器時,你會始終得到NULL:
[java] view plain copy  print?在CODE上檢視程式碼片派生到我的程式碼片
  1. System.out.println(String.class.getClassLoader());//null
  2. System.out.println(Object.class.getClassLoader());//null
  3. System.out.println(Math.class.getClassLoader());//null
  4. System.out.println(System.class.getClassLoader());//null


Step 3. 建立JVM 啟動器例項 Launcher,並取得類載入器ClassLoader

上述步驟完成,JVM基本執行環境就準備就緒了。接著,我們要讓JVM工作起來了:執行我們定義的程式 org.luanlouis,jvm.load.Main

此時,JVM虛擬機器呼叫已經載入在方法區的類sun.misc.Launcher 的靜態方法getLauncher(),  獲取sun.misc.Launcher 例項:

[java] view plain copy  print?在CODE上檢視程式碼片派生到我的程式碼片
  1. sun.misc.Launcher launcher = sun.misc.Launcher.getLauncher(); //獲取Java啟動器
  2. ClassLoader classLoader = launcher.getClassLoader();          //獲取類載入器ClassLoader用來載入class到記憶體來

sun.misc.Launcher 使用了單例模式設計,保證一個JVM虛擬機器內只有一個sun.misc.Launcher例項。
在Launcher的內部,其定義了兩個類載入器(ClassLoader),分別是sun.misc.Launcher.ExtClassLoadersun.misc.Launcher.AppClassLoader,這兩個類載入器分別被稱為拓展類載入器(Extension ClassLoader) 和 應用類載入器(Application ClassLoader).如下圖所示:

圖例註釋:除了引導類載入器(Bootstrap Class Loader )的所有類載入器,都有一個能力,就是判斷某一個類是否被引導類載入器載入過,如果載入過,可以直接返回對應的Class<T> instance,如果沒有,則返回null.  圖上的指向引導類載入器的虛線表示類載入器的這個有限的訪問 引導類載入器的功能。

      此時的  launcher.getClassLoader() 方法將會返回 AppClassLoader 例項,AppClassLoaderExtClassLoader作為自己的父載入器。

AppClassLoader載入類時,會首先嚐試讓父載入器ExtClassLoader進行載入,如果父載入器ExtClassLoader載入成功,則AppClassLoader直接返回父載入器ExtClassLoader載入的結果;如果父載入器ExtClassLoader載入失敗,AppClassLoader則會判斷該類是否是引導的系統類(即是否是通過Bootstrap類載入器載入,這會呼叫Native方法進行查詢);若要載入的類不是系統引導類,那麼ClassLoader將會嘗試自己載入,載入失敗將會丟擲“ClassNotFoundException”。

具體AppClassLoader的工作流程如下所示:


雙親委派模型(parent-delegation model):
上面討論的應用類載入器AppClassLoader的載入類的模式就是我們常說的雙親委派模型(parent-delegation model).
對於某個特定的類載入器而言,應該為其指定一個父類載入器,當用其進行載入類的時候:
1. 委託父類載入器幫忙載入;
2. 父類載入器載入不了,則查詢引導類載入器有沒有載入過該類;
3. 如果引導類載入器沒有載入過該類,則當前的類載入器應該自己載入該類;
4. 若載入成功,返回 對應的Class<T> 物件;若失敗,丟擲異常“ClassNotFoundException”。
請注意:
雙親委派模型中的"雙親"並不是指它有兩個父類載入器的意思,一個類載入器只應該有一個父載入器。上面的步驟中,有兩個角色:
1. 父類載入器(parent classloader):它可以替子載入器嘗試載入類
2. 引導類載入器(bootstrap classloader): 子類載入器只能判斷某個類是否被引導類載入器載入過,而不能委託它載入某個類;換句話說,就是子類載入器不能接觸到引導類載入器,引導類載入器對其他類載入器而言是透明的。

一般情況下,雙親載入模型如下所示:



Step 4. 使用類載入器ClassLoader載入Main類

通過 launcher.getClassLoader()方法返回AppClassLoader例項,接著就是AppClassLoader載入 org.luanlouis.jvm.load.Main類的時候了。

[java] view plain copy  print?在CODE上檢視程式碼片派生到我的程式碼片
  1. ClassLoader classloader = launcher.getClassLoader();//取得AppClassLoader類
  2. classLoader.loadClass("org.luanlouis.jvm.load.Main");//載入自定義類

上述定義的org.luanlouis.jvm.load.Main類被編譯成org.luanlouis.jvm.load.Main class二進位制檔案,這個class檔案中有一個叫常量池(Constant Pool)的結構體來儲存該class的常亮資訊。常量池中有CONSTANT_CLASS_INFO型別的常量,表示該class中聲明瞭要用到那些類:


當AppClassLoader要載入 org.luanlouis.jvm.load.Main類時,會去檢視該類的定義,發現它內部宣告使用了其它的類: sun.security.pkcs11.P11Util、java.lang.Object、java.lang.System、java.io.PrintStream、java.lang.Class;org.luanlouis.jvm.load.Main類要想正常工作,首先要能夠保證這些其內部宣告的類載入成功。所以AppClassLoader要先將這些類載入到記憶體中。(注:為了理解方便,這裡沒有考慮懶載入的情況,事實上的JVM載入類過程比這複雜的多)

載入順序:

1. 載入java.lang.Object、java.lang.System、java.io.PrintStream、java,lang.Class

     AppClassLoader嘗試載入這些類的時候,會先委託ExtClassLoader進行載入;而ExtClassLoader發現不是其載入範圍,其返回null;AppClassLoader發現父類載入器ExtClassLoader無法載入,則會查詢這些類是否已經被BootstrapClassLoader載入過,結果表明這些類已經被BootstrapClassLoader載入過,則無需重複載入,直接返回對應的Class<T>例項;

2. 載入sun.security.pkcs11.P11Util

    此在{JRE_HOME}/lib/ext/sunpkcs11.jar包內,屬於ExtClassLoader負責載入的範疇。AppClassLoader嘗試載入這些類的時候,會先委託ExtClassLoader進行載入;而ExtClassLoader發現其正好屬於載入範圍,故ExtClassLoader負責將其載入到記憶體中。ExtClassLoader在載入sun.security.pkcs11.P11Util時也分析這個類內都使用了哪些類,並將這些類先載入記憶體後,才開始載入sun.security.pkcs11.P11Util,載入成功後直接返回對應的Class<sun.security.pkcs11.P11Util>例項;

3. 載入org.luanlouis.jvm.load.Main

  AppClassLoader嘗試載入這些類的時候,會先委託ExtClassLoader進行載入;而ExtClassLoader發現不是其載入範圍,其返回null;AppClassLoader發現父類載入器ExtClassLoader無法載入,則會查詢這些類是否已經被BootstrapClassLoader載入過。而結果表明BootstrapClassLoader 沒有載入過它,這時候AppClassLoader只能自己動手負責將其載入到記憶體中,然後返回對應的Class<org.luanlouis.jvm.load.Main>例項引用;

以上三步驟都成功,才表示classLoader.loadClass("org.luanlouis.jvm.load.Main")完成,上述操作完成後,JVM記憶體方法區的格局會如下所示:


如上圖所示:

  • JVM方法區的類資訊區是按照類載入器進行劃分的,每個類載入器會維護自己載入類資訊;
  • 某個類載入器在載入相應的類時,會相應地在JVM記憶體堆(Heap)中建立一個對應的Class<T>,用來表示訪問該類資訊的入口

Step 5. 使用Main類的main方法作為程式入口執行程式

Step 6. 方法執行完畢,JVM銷燬,釋放記憶體

三、類載入器有哪些?其組織結構是怎樣的?

             類載入器(Class Loader):顧名思義,指的是可以載入類的工具。JVM自身定義了三個類載入器:引導類載入器(Bootstrap Class Loader)、拓展類載入器(Extension Class Loader )、應用載入器(Application Class Loader)。當然,我們有時候也會自己定義一些類載入器來滿足自身的需要。

            引導類載入器(Bootstrap Class Loader): 該類載入器使JVM使用C/C++底層程式碼實現的載入器,用以載入JVM執行時所需要的系統類,這些系統類在{JRE_HOME}/lib目錄下。由於類載入器是使用平臺相關的底層C/C++語言實現的, 所以該載入器不能被Java程式碼訪問到。但是,我們可以查詢某個類是否被引導類載入器載入過。我們經常使用的系統類如:java.lang.String,java.lang.Object,java.lang*....... 這些都被放在 {JRE_HOME}/lib/rt.jar包內, 當JVM系統啟動的時候,引導類載入器會將其載入到 JVM記憶體的方法區中。

           拓展類載入器(Extension Class Loader): 該載入器是用於載入 java 的拓展類 ,拓展類一般會放在 {JRE_HOME}/lib/ext/ 目錄下,用來提供除了系統類之外的額外功能。拓展類載入器是是整個JVM載入器的Java程式碼可以訪問到的類載入器的最頂端,即是超級父載入器,拓展類載入器是沒有父類載入器的。

應用類載入器(Applocatoin Class Loader): 該類載入器是用於載入使用者程式碼,是使用者程式碼的入口。我經常執行指令 java   xxx.x.xxx.x.x.XClass , 實際上,JVM就是使用的AppClassLoader載入 xxx.x.xxx.x.x.XClass 類的。應用類載入器將拓展類載入器當成自己的父類載入器,當其嘗試載入類的時候,首先嚐試讓其父載入器-拓展類載入器載入;如果拓展類載入器載入成功,則直接返回載入結果Class<T> instance,載入失敗,則會詢問是否引導類載入器已經載入了該類;只有沒有載入的時候,應用類載入器才會嘗試自己載入。由於xxx.x.xxx.x.x.XClass是整個使用者程式碼的入口,在Java虛擬機器規範中,稱其為 初始類(Initial Class).

    
           使用者自定義類載入器(Customized Class Loader):使用者可以自己定義類載入器來載入類。所有的類載入器都要繼承java.lang.ClassLoader類。

           

四、雙親載入模型的邏輯和底層程式碼實現是怎樣的?

            上面已經不厭其煩地講解什麼是雙親載入模型,以及其機制是什麼,這些東西都是可以通過底層程式碼檢視到的。

             我們也可以通過JDK原始碼看java.lang.ClassLoader的核心方法 loadClass()的實現:

[java] view plain copy  print?在CODE上檢視程式碼片派生到我的程式碼片
  1. //提供class類的二進位制名稱表示,載入對應class,載入成功,則返回表示該類對應的Class<T> instance 例項
  2. public Class<?> loadClass(String name) throws ClassNotFoundException {  
  3.     return loadClass(name, false);  
  4. }  
  5. protected Class<?> loadClass(String name, boolean resolve)  
  6.     throws ClassNotFoundException  
  7. {  
  8.     synchronized (getClassLoadingLock(name)) {  
  9.         // 首先,檢查是否已經被當前的類載入器記載過了,如果已經被載入,直接返回對應的Class<T>例項
  10.         Class<?> c = findLoadedClass(name);  
  11.             //初次載入
  12.             if (c == null) {  
  13.             long t0 = System.nanoTime();  
  14.             try {  
  15.                 if (parent != null) {  
  16.                     //如果有父類載入器,則先讓父類載入器載入
  17.                     c = parent.loadClass(name, false);  
  18.                 } else {  
  19.                     // 沒有父載入器,則檢視是否已經被引導類載入器載入,有則直接返回
  20.                     c = findBootstrapClassOrNull(name);  
  21.                 }  
  22.             } catch (ClassNotFoundException e) {  
  23.                 // ClassNotFoundException thrown if class not found
  24.                 // from the non-null parent class loader
  25.             }  
  26.             // 父載入器載入失敗,並且沒有被引導類載入器載入,則嘗試該類載入器自己嘗試載入
  27.             if (c == null) {  
  28.                 // If still not found, then invoke findClass in order
  29.                 // to find the class.
  30.                 long t1 = System.nanoTime();  
  31.                 // 自己嘗試載入
  32.                 c = findClass(name);  
  33.                 // this is the defining class loader; record the stats
  34.                 sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);  
  35.                 sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);  
  36.                 sun.misc.PerfCounter.getFindClasses().increment();  
  37.             }  
  38.         }  
  39.         //是否解析類 
  40.         if (resolve) {  
  41.             resolveClass(c);  
  42.         }  
  43.         return c;  
  44. 相關推薦

    JVM載入機制載入過程

    0、前言 讀完本文,你將瞭解到: 一、為什麼說Jabalpur語言是跨平臺的 二、Java虛擬機器啟動、載入類過程分析 三、類載入器有哪些?其組織結構是怎樣的? 四、雙親載入模型的邏輯和底層程式碼實現是怎樣的? 五、類載入器與Class<T> 

    《Java虛擬機器原理圖解》5. JVM載入機制載入過程

    0、前言 讀完本文,你將瞭解到: 一、為什麼說Jabalpur語言是跨平臺的 二、Java虛擬機器啟動、載入類過程分析 三、類載入器有哪些?其組織結構是怎樣的? 四、雙親載入模型的邏輯和底層程式碼實現是怎樣的? 五、類載入器與Class<T>  例項的關係

    (二十七)JVM加載機制加載過程

    有時 重復加載 win ppc context 類的定義 字符集 area main方法 一、Java虛擬機啟動、加載類過程分析 下面我將定義一個非常簡單的java程序並運行它,來逐步分析java虛擬機啟動的過程。 package org.luanlouis.jvm.lo

    圖解JVM載入機制載入過程

    0、前言 讀完本文,你將瞭解到: 一、為什麼說Jabalpur語言是跨平臺的 二、Java虛擬機器啟動、載入類過程分析 三、類載入器有哪些?其組織結構是怎樣的? 四、雙親載入模型的邏輯和底層程式碼實現是怎樣的? 五、類載入器與Class<T&

    十、JAVA多執行緒:JVM載入(自動載入、雙親委託機制載入名稱空間、執行時包、的解除安裝等)

      Jvm提供了三大內建的類載入器,不同的類載入器負責將不同的類載入到記憶體之中 根載入器(Bootstrap ClassLoader) 是最頂層的載入器,是由C++編寫的,主要負責虛擬機器核心類庫的載入,如整個java.lang包,根載入器是獲取不到引用的,因此

    java載入和雙親委派載入機制

    java類載入器分類詳解  1、Bootstrap ClassLoader:啟動類載入器,也叫根類載入器,負責載入java的核心類庫,例如(%JAVA_HOME%/lib)目錄下的rt.jar(包含Sy

    Java基礎-加載機制自定義Java加載(ClassLoader)

    定義類 方式 blog 之前 www 筆記 通過 反射 加載機制          Java基礎-類加載機制與自定義類Java類加載器(ClassLoader)                                     作者:尹正傑 版權聲明:原創作品,謝絕轉

    Class 載入,內部類載入實驗

    最近實驗通過jdk編譯後的Class多出一個內部類class檔案,美元$符號的class檔案,如A.class ,A$B.class,載入A.class總是報錯。 除了載入內部類方法,還有就是把class檔案打包成jar,通過自定義URLClassLoader  載入jar

    使用自定義載入和預設載入載入兩個相同的

    package test.ask; import java.lang.reflect.Field; public class Main {     public static void main(String[] args) {         try{          

    2 :為什麼要載入載入做了什麼,載入過程

    為什麼要使用類載入器? Java語言裡,類載入都是在程式執行期間完成的,這種策略雖然會令類載入時稍微增加一些效能開銷,但是會給java應用程式提供高度的靈活性。例如:1.編寫一個面向介面的應用程式,可能等到執行時再指定其實現的子類;2.使用者可以自定義一個類載入器,讓程

    Tomcat6載入定義(class載入順序)

    Class Loader Definitions As indicated in the diagram above, Tomcat 6 creates the following class loaders as it is initialized: (譯:就像上面

    Java載入--Tomcat的載入架構

    文章引用: 主流的Java Web伺服器(也就是Web容器),如Tomcat、Jetty、WebLogic、WebSphere或其他筆者沒有列舉的伺服器,都實現了自己定義的類載入器(一般都不止一個)。因為一個功能健全的Web容器,要解決如下幾個問題: 1

    Jquery偽選擇不加空格的區別

    在學習《鋒利的jQuery》時,發現偽類選擇器加不加空格得到的結果截然不同,特總結如下,個人觀點,如有錯誤,歡迎指正: 目錄 加空格的偽類選擇器 不加空格的偽類選擇器 原因及使用空格的總結 正文 本文使用的html程式碼如下:

    第18章 加載機制反射

    類加載 4.2 加載器 成員變量 數組 4.3 class類 類信息 voc 第18章 類加載機制與反射 18.1 類的加載、連接和初始化 18.1.1 JVM和類 18.1

    django視圖介紹視圖裝飾

    修改 num 導入 get post strip() 單獨 get () rev django框架是MTV框架 views.py中def定義的視圖是基於函數的視圖,class定義的視圖是基於類的視圖。 --- 分為普通類視圖,通用類視圖 - 特定的Http方法,get

    非同步載入遊戲場景非同步載入遊戲資源進度條

    在Hierarchy檢視中我們可以看到該場景中“天生”的所有遊戲物件。天生的意思就是執行程式前該場景中就已經存在的所有遊戲物件。然後這些物件就會在執行完Application.LoadLevel(“yourScene”);方法後加載至記憶體當中。如果該場景中的遊戲物件過多那麼瞬間將會出現卡一下的情況

    jquery的點選事件,非動態載入點選動態載入點選

    jquery的點選事件,非動態載入點選與動態載入點選寫法不同 1.非動態載入點選 <div id="clickdemo"> <ul> <li>1<li> </ul> </div>點選1觸發 $

    jquery 選擇選擇 祖父 子選擇 兄弟選擇

    選擇子類: div 下的img元素     $("div").children("img") 選擇父類:div的所有父類        $("div").parent('') 選擇 祖父: p

    構造呼叫父構造的順序問題

    一 super呼叫父類構造器 子類構造器總會呼叫父類構造器。 如果子類構造器沒有顯示使用super呼叫父類構造器,子類構造器預設會呼叫父類無參構造器。 建立一個子類例項時,總會呼叫最頂層父類構造器。 二 構造器呼叫順序示例 1 程式碼示例 class Creature { public Creature(

    css3偽選擇--動態偽選擇

    動態偽類並不存在於html中,只有當用戶和網站互動的時候才會體現出來。動態偽類包含兩種,一種是在連結中常看到的錨點偽類,一種是使用者行為偽類。 連結偽類選擇器:E:link(未被訪問過)     和