JVM之--類載入器子系統

2020-08-12 21:35:55

JVM細節框架圖

在这里插入图片描述本文針對Class Loader SubSystem這一塊展開講解類載入子系統的工作流程

類載入子系統作用

  • 類載入子系統負責從檔案系統或者網路中載入class檔案,class檔案在檔案開頭有特定的檔案標識即16進位制CA FE BA BE;
  • 載入後的Class類資訊存放於一塊稱爲方法區的記憶體空間。除了類資訊之外,方法區還會存放執行時常數池資訊,可能還包括字串字面量和數位常數(這部分常數資訊是Class檔案中常數池部分的記憶體對映)
  • ClassLoader只負責class檔案的載入,至於它是否可以執行,則由Execution Engine決定
  • 如果呼叫構造器範例化物件,則其範例存放在堆區
    來一張經典的JVM記憶體結構圖:其中類載入器的工作範圍只限於下圖的左半部分,不包含呼叫構造器範例化物件
    在这里插入图片描述

類載入子系統功能細分

类加载子系统

載入

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

鏈接

鏈接模組分爲三塊,即驗證、準備、解析

驗證

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

2.主要包括四種驗證,檔案格式驗證,源數據驗證,位元組碼驗證,符號參照驗證。

準備

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

2.這裏不包含用final修飾的sttic,因爲final在編譯的時候就會分配了,準備階段會顯式初始化;

3.之類不會爲範例變數分配初始化,類變數會分配在方法去中,而範例變數是會隨着物件一起分配到java堆中。

解析

1.將常數池內的符號參照轉換爲直接參照的過程。

2.事實上,解析操作網晚會伴隨着jvm在執行完初始化之後再執行

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

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

初始化

初始化階段就是執行類構造器方法clinit()的過程

1.clinit()即「class or interface initialization method」,注意他並不是指構造器init()

2.此方法不需要定義,是javac編譯器自動收集類中的所有類變數的賦值動作和靜態程式碼塊中的語句合併而來。

3.我們注意到如果沒有靜態變數c,那麼位元組碼檔案中就不會有clinit方法
在这里插入图片描述

在这里插入图片描述
構造器方法clinit()中指令按語句在原始檔中出現的順序執行
在这里插入图片描述

類載入器分類

1.JVM支援兩種型別的載入器,分別爲引導類載入器C/C++實現(BootStrap ClassLoader)和自定義類載入器由Java實現

2.從概念上來講,自定義類載入器一般指的是程式中由開發人員自定義的一類類載入器,但是java虛擬機器規範卻沒有這麼定義,而是將所有派生於抽象類ClassLoader的類載入器都劃分爲自定義類載入器。
在这里插入图片描述3.注意上圖中的載入器劃分關係爲包含關係,並不是繼承關係

4.按照這樣的載入器的型別劃分,在程式中我們最常見的類載入器是:引導類載入器BootStrapClassLoader、自定義類載入器(Extension Class Loader、System Class Loader、User-Defined ClassLoader)

自定義類和核心類的類載入器
1.對於使用者自定義類來說:將使用系統類System Class Loader載入器中的AppClassLoader進行載入

2.java核心類庫都是使用引導類載入器BootStrapClassLoader載入的

虛擬機器自帶的類載入器

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

1.這個類載入使用C/C++語言實現的,巢狀在JVM內部

2.它用來載入java的核心庫(JAVA_HOME/jre/lib/rt.jar/resources.jar或sun.boot.class.path路徑下的內容),用於提供JVM自身需要的類

3.並不繼承自java.lang.ClassLoader,沒有父載入器

4.載入拓展類和應用程式類載入器,並指定爲他們的父載入器,即ClassLoader

5.出於安全考慮,BootStrap啓動類載入器只載入包名爲java、javax、sun等開頭的類

拓展類載入器(Extension ClassLoader)

1.由java語言編寫 ,由sun.misc.Launcher$ExtClassLoader實現。

2.派生於ClassLoader類

3.從java.ext.dirs系統屬性所指定的目錄中載入類庫,或從JDK的安裝目錄的jre/lib/ext子目錄(擴充套件目錄)下載入類庫。如果使用者建立的JAR放在此目錄下,也會由拓展類載入器自動載入

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

1.由java語言編寫, 由sun.misc.Launcher$AppClassLoader實現。

2.派生於ClassLoader類

3.它負責載入環境變數classpath或系統屬性 java.class.path指定路徑下的類庫

4.該類載入器是程式中預設的類載入器,一般來說,java應用的類都是由它來完成載入

5.通過ClassLoader#getSystemClassLoader()方法可以獲取到該類載入器

知識擴充套件:啓動類載入器BootStrapClassLoader能夠載入的api路徑有
在这里插入图片描述

爲什麼要使用使用者自定義類載入器

1.隔離載入類

2.修改類載入的方式

3.拓展載入源

4.防止原始碼泄漏
在这里插入图片描述

關於ClassLoader類

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

常用方法
在这里插入图片描述
獲取ClassLoader的方法
在这里插入图片描述

雙親委派機制 機製

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

工作原理

在这里插入图片描述

例子

在这里插入图片描述
執行main方法,那main方法所在的類就要被載入,但是由於雙親委派機制 機製,它需要一層層向上委託,最後到達了引導類載入器,引導類載入器一看,是java.lang包下的String類,誤把這個String類當成了java核心類下的那個String。就執行了載入,但是核心包下的String類沒有main方法,所以就報了上面的錯誤。

優勢

1.避免類的重複載入

2.保護程式安全,防止核心API被隨意修改

啓動類載入器可以搶在標準擴充套件類裝載器之前去裝載類,而標準擴充套件類裝載器可以搶在類路徑載入器之前去裝載那個類,類路徑裝載 器又可以搶在自定義類載入器之前去載入它。所以Java虛擬機器先從最可信的Java核心API查詢型別,這是爲了防止不可靠的類扮演被信任的類,試想一 下,網路上有個名叫java.lang.Integer的類,它是某個駭客爲了想混進java.lang包所起的名字,實際上裏面含有惡意代碼,但是這種 伎倆在雙親模式載入體系結構下是行不通的,因爲網路類載入器在載入它的時候,它首先呼叫雙親類載入器,這樣一直向上委託,直到啓動類載入器,而啓動類載入 器在覈心Java API裡發現了這個名字的類,所以它就直接載入Java核心API的java.lang.Integer類,然後將這個類返回,所以自始自終網路上的 java.lang.Integer的類是不會被載入的。

3.保證核心API包的存取許可權

但是如果這個移動程式碼不是去試圖替換一個被信任的類(就是前面說的那種情況),而是想在一個被信任的包中插入一個全新的型別,情況會怎樣呢?比如一個名爲 java.lang.Xionger的類,經過雙親委託模式,最終類裝載器試圖從網路上下載這個類,因爲網路類裝載器的雙親們都沒有這個類(當然沒有了,因爲 是病毒嘛)。假設成功下載了這個類,那你肯定會想,Xionger和lang下的其他類都在java.lang包下,暗示這個類是Java API的一部分,那麼是不是也擁有修改Java.lang包中數據的許可權呢?答案當然不是,因爲要取得存取和修改java.lang包中的權 限,java.lang.Xionger和java.lang下其他類必須是屬於同一個執行時包,什麼是執行時包?執行時包是指由同一個類裝載器裝載的、屬 於同一個包的、多個型別的集合。考慮一下,java.lang.Xionger和java.lang其他類是同一個類裝載器裝載的嗎?不是 的!java.lang.Xionger是由網路類裝載器裝載的!

自定義類:java.lang.MeDsh(java.lang包需要存取許可權,阻止我們用包名自定義類)
  在这里插入图片描述

JVM中兩個class物件是否來自同一個類?

1.在jvm中兩個class物件是否來自同一個類必須存在兩個必要條件

  • 類的完整類名必須一致,包括包名

  • 即使類的完整類名一致,同時要求載入這個類的ClassLoader(指ClassLoader範例物件)必須相同;是引導類載入器、還是定義類載入器

2.換句話說,在jvm中,即使這兩個類物件(class物件)來源同一個Class檔案,被同一個虛擬機器所載入,但只要載入它們的ClassLoader範例物件不同,那麼這兩個類物件也是不相等的.

3.對類載入器的參照,JVM必須知道一個型別是由啓動類載入器載入的還是由使用者類載入器載入的。如果一個型別由使用者類載入器載入的,那麼JVM會將這個類載入器的一個參照作爲型別資訊的一部分儲存在方法區中。當解析一個型別到另一個型別的參照的時候,JVM需要保證兩個型別的載入器是相同的。

類的主動使用和被動使用

java程式對類的使用方式分爲:主動使用和被動使用,即是否呼叫了clinit()方法

主動使用在類載入系統中的第三階段initialization即初始化階段呼叫了clinit()方法

而被動使用不會去呼叫

主動使用,分爲七種情況

1.建立類的範例

2.存取某各類或介面的靜態變數,或者對靜態變數賦值

3.呼叫類的靜態方法

4.反射 比如Class.forName(com.dsh.jvm.xxx)

5.初始化一個類的子類

6.java虛擬機器啓動時被標明爲啓動類的類

7.JDK 7 開始提供的動態語言支援:java.lang.invoke.MethodHandle範例的解析結果REF_getStatic、REF_putStatic、REF_invokeStatic控制代碼對應的類沒有初始化,則初始化

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

come from 尚硅谷—> https://www.bilibili.com/video/BV1PJ411n7xZ?p=39
and—> https://www.cnblogs.com/yanl55555/p/12611133.html