一、類加載器子系統作用
類加載器子系統負責從檔案系統或者網絡中加載 class 檔案,class 檔案在檔案開頭有特定的檔案辨別。
ClassLoader 隻負責 class 檔案的加載,至于它是否可以運作,則由 Execution Engine 決定。
加載的類資訊存放于一塊稱為方法區的記憶體空間。除了類的資訊外,方法區中還會存放運作時常量池資訊,可能還包括字元串字面量和數字常量(這部分常量資訊是 class 檔案中常量池部分的記憶體映射)
二、類加載器ClasLoader角色
class file 存在于本地硬碟上,可以了解為設計師畫在紙上的模闆,而最終這個模闆在執行的時候是要加載到JVM 當中來根據這個檔案執行個體化出 n 個一模一樣的執行個體。
class file 加載到 JVM 中,被稱為 DNA 中繼資料模闆,放在方法區。
在 class 檔案 -> JVM -> 最終成為中繼資料模闆,此過程就要一個運輸工具(類裝載器Class Loader),扮演一個快遞員的角色。
三、類的加載過程
public class HelloLoader {
public static void main(String[] args) {
System.out.println("Hello World!");
}
}
四、加載階段
通過一個類的全限定名擷取定義此類的二進制位元組流
将這個位元組流所代表的靜态存儲結構轉化為方法區的運作時資料結構
在記憶體中生成一個代表這個類的java.lang.Class對象,作為方法區這個類的各種資料的通路入口
加載class檔案的方式
從本地系統中直接加載
通過網絡擷取,典型場景:Web Applet
從zip壓縮包中讀取,成為日後jar、war格式的基礎
運作時計算生成,使用最多的是:動态代理技術
由其他檔案生成,典型場景:JSP應用
從專有資料庫中提取.class檔案,比較少見
從加密檔案中擷取,典型的防Class檔案被反編譯的保護措施
五、連結階段
1、驗證(Verify)
目的在子確定Class檔案的位元組流中包含資訊符合目前虛拟機要求,保證被加載類的正确性,不會危害虛拟機自身安全。
主要包括四種驗證,檔案格式驗證,中繼資料驗證,位元組碼驗證,符号引用驗證。
2、準備(Prepare)
為類變量配置設定記憶體并且設定該類變量的預設初始值,即零值。
這裡不包含用final修飾的static,因為final在編譯的時候就會配置設定了,準備階段會顯式初始化;
這裡不會為執行個體變量配置設定初始化,類變量會配置設定在方法區中,而執行個體變量是會随着對象一起配置設定到Java堆中。
3、解析(Resolve)
将常量池内的符号引用轉換為直接引用的過程。
事實上,解析操作往往會伴随着JVM在執行完初始化之後再執行。
符号引用就是一組符号來描述所引用的目标。符号引用的字面量形式明确定義在《java虛拟機規範》的Class檔案格式中。直接引用就是直接指向目标的指針、相對偏移量或一個間接定位到目标的句柄。
解析動作主要針對類或接口、字段、類方法、接口方法、方法類型等。對應常量池中的CONSTANT_Class_info,CONSTANT_Fieldref_info、CONSTANT_Methodref_info等。
六、初始化階段
初始化階段就是執行類構造器方法<clinit>()的過程。
此方法不需定義,是javac編譯器自動收集類中的所有類變量的指派動作和靜态代碼塊中的語句合并而來。如果沒有這些時不會有clinit方法
構造器方法中指令按語句在源檔案中出現的順序執行。
<clinit>()不同于類的構造器。(關聯:構造器是虛拟機視角下的<init>())
若該類具有父類,JVM會保證子類的<clinit>()執行前,父類的<clinit>()已經執行完畢。
虛拟機必須保證一個類的<clinit>()方法在多線程下被同步加鎖。
七、虛拟機加載器
1、啟動類加載器(引導類加載器,Bootstrap ClassLoader)
這個類加載使用C/C++語言實作的,嵌套在JVM内部。
它用來加載Java的核心庫(JAVA_HOME/jre/lib/rt.jar、resources.jar或sun.boot.class.path路徑下的内容),用于提供JVM自身需要的類,并不繼承自ava.lang.ClassLoader,沒有父加載器。加載擴充類和應用程式類加載器,并指定為他們的父類加載器。
出于安全考慮,Bootstrap啟動類加載器隻加載包名為java、javax、sun等開頭的類
具體加載
2、擴充類加載器(Extension ClassLoader)
Java語言編寫,由sun.misc.Launcher$ExtClassLoader實作。
父類加載器為啟動類加載器
從java.ext.dirs系統屬性所指定的目錄中加載類庫,或從JDK的安裝目錄的jre/1ib/ext子目錄(擴充目錄)下加載類庫。如果使用者建立的JAR放在此目錄下,也會自動由擴充類加載器加載。
具體加載下面位置類
3、應用程式類加載器(系統類加載器,AppClassLoader)
java語言編寫,由sun.misc.LaunchersAppClassLoader實作
父類加載器為擴充類加載器
它負責加載環境變量classpath或系統屬性java.class.path指定路徑下的類庫
該類加載是程式中預設的類加載器,一般來說,Java應用的類都是由它來完成加載
通過ClassLoader#getSystemclassLoader() 方法可以擷取到該類加載器
八、使用者自定義類加載器
在Java的日常應用程式開發中,類的加載幾乎是由上述3種類加載器互相配合執行的,在必要時,我們還可以自定義類加載器,來定制類的加載方式。
● 隔離加載類
● 修改類加載的方式
● 擴充加載源
● 防止源碼洩漏
使用者自定義類加載器實作步驟:
1. 繼承抽象類ava.lang.ClassLoader類的方式,實作自己的類加載器,以滿足一些特殊的需求
2. 在JDK1.2之前,在自定義類加載器時,總會去繼承ClassLoader類并重寫loadClass() 方法,進而實作自定義的類加載類,但是在JDK1.2之後已不再建議使用者去覆寫loadclass() 方法,而是建議把自定義的類加載邏輯寫在findClass()方法中
3. 在編寫自定義類加載器時,如果沒有太過于複雜的需求,可以直接繼承URLClassLoader類,這樣就可以避免自己去編寫findClass() 方法及其擷取位元組碼流的方式,使自定義類加載器編寫更加簡潔。
九、雙親委派機制
Java虛拟機對class檔案采用的是按需加載的方式,也就是說當需要使用該類時才會将它的class檔案加載到記憶體生成class對象。而且加載某個類的class檔案時,Java虛拟機采用的是雙親委派模式,即把請求交由父類處理,它是一種任務委派模式。
1、工作原理
1)如果一個類加載器收到了類加載請求,它并不會自己先去加載,而是把這個請求委托給父類的加載器去執行;
2)如果父類加載器還存在其父類加載器,則進一步向上委托,依次遞歸,請求最終将到達頂層的啟動類加載器;
3)如果父類加載器可以完成類加載任務,就成功傳回,倘若父類加載器無法完成此加載任務,子加載器才會嘗試自己去加載,這就是雙親委派模式。
2、雙親委派舉例
當我們加載jdbc.jar 用于實作資料庫連接配接的時候,首先我們需要知道的是 jdbc.jar是基于SPI接口進行實作的,是以在加載的時候,會進行雙親委派,最終從根加載器中加載 SPI核心類,然後在加載SPI接口類,接着在進行反向委派,通過線程上下文類加載器進行實作類jdbc.jar的加載。
3、優勢
避免類的重複加載
保護程式安全,防止核心API被随意篡改
○ 自定義類:java.lang.String
○ 自定義類:java.lang.ShkStart(報錯:阻止建立 java.lang開頭的類)
十、對類加載器的引用
JVM必須知道一個類型是由啟動加載器加載的還是由使用者類加載器加載的。如果一個類型是由使用者類加載器加載的,那麼JVM會将這個類加載器的一個引用作為類型資訊的一部分儲存在方法區中。當解析一個類型到另一個類型的引用的時候,JVM需要保證這兩個類型的類加載器是相同的。
十一、類的主動使用和被動使用
主動使用
● 建立類的執行個體
● 通路某個類或接口的靜态變量,或者對該靜态變量指派
● 調用類的靜态方法
● 反射(比如:Class.forName(""))
● 初始化一個類的子類
● Java虛拟機啟動時被标明為啟動類的類
● JDK 7 開始提供的動态語言支援:
java.lang.invoke.MethodHandle執行個體的解析結果
REF_getStatic、REF_putStatic、REF_invokeStatic句柄對應的類沒有初始化,則初始化
除了以上七種情況,其他使用Java類的方式都被看作是對類的被動使用,都不會導緻類的初始化。
附:
對給定的class檔案提供的位元組代碼進行反編譯
javap -v Test01ClassInit.class
Classfile /E:/code/01base/00JVM/target/classes/com/ghbs/jvm01/Test01ClassInit.class
Last modified 2022-10-16; size 668 bytes
MD5 checksum 0e3834313ef891c744b4a55cf1ef51b8
Compiled from "Test01ClassInit.java"
public class com.ghbs.jvm01.Test01ClassInit
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #6.#24 // java/lang/Object."<init>":()V
#2 = Fieldref #25.#26 // java/lang/System.out:Ljava/io/PrintStream;
#3 = Fieldref #5.#27 // com/ghbs/jvm01/Test01ClassInit.number:I
#4 = Methodref #28.#29 // java/io/PrintStream.println:(I)V
#5 = Class #30 // com/ghbs/jvm01/Test01ClassInit
#6 = Class #31 // java/lang/Object
#7 = Utf8 number
#8 = Utf8 I
#9 = Utf8 <init>
#10 = Utf8 ()V
#11 = Utf8 Code
#12 = Utf8 LineNumberTable
#13 = Utf8 LocalVariableTable
#14 = Utf8 this
#15 = Utf8 Lcom/ghbs/jvm01/Test01ClassInit;
#16 = Utf8 main
#17 = Utf8 ([Ljava/lang/String;)V
#18 = Utf8 args
#19 = Utf8 [Ljava/lang/String;
#20 = Utf8 MethodParameters
#21 = Utf8 <clinit>
#22 = Utf8 SourceFile
#23 = Utf8 Test01ClassInit.java
#24 = NameAndType #9:#10 // "<init>":()V
#25 = Class #32 // java/lang/System
#26 = NameAndType #33:#34 // out:Ljava/io/PrintStream;
#27 = NameAndType #7:#8 // number:I
#28 = Class #35 // java/io/PrintStream
#29 = NameAndType #36:#37 // println:(I)V
#30 = Utf8 com/ghbs/jvm01/Test01ClassInit
#31 = Utf8 java/lang/Object
#32 = Utf8 java/lang/System
#33 = Utf8 out
#34 = Utf8 Ljava/io/PrintStream;
#35 = Utf8 java/io/PrintStream
#36 = Utf8 println
#37 = Utf8 (I)V
{
public com.ghbs.jvm01.Test01ClassInit();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 6: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcom/ghbs/jvm01/Test01ClassInit;
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=1, args_size=1
0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
3: getstatic #3 // Field number:I
6: invokevirtual #4 // Method java/io/PrintStream.println:(I)V
9: return
LineNumberTable:
line 14: 0
line 15: 9
LocalVariableTable:
Start Length Slot Name Signature
0 10 0 args [Ljava/lang/String;
MethodParameters:
Name Flags
args
static {};
descriptor: ()V
flags: ACC_STATIC
Code:
stack=1, locals=0, args_size=0
0: iconst_2
1: putstatic #3 // Field number:I
4: iconst_1
5: putstatic #3 // Field number:I
8: return
LineNumberTable:
line 8: 0
line 11: 4
}
SourceFile: "Test01ClassInit.java"
E:\code\01base\00JVM\target\classes\com\ghbs\jvm01>
E:\code\01base\00JVM\target\classes\com\ghbs\jvm01>
E:\code\01base\00JVM\target\classes\com\ghbs\jvm01>
E:\code\01base\00JVM\target\classes\com\ghbs\jvm01>javap -v Test01ClassInit.class
Classfile /E:/code/01base/00JVM/target/classes/com/ghbs/jvm01/Test01ClassInit.class
Last modified 2022-10-16; size 671 bytes
MD5 checksum 40c1efe2199adbecd985780dd2eac816
Compiled from "Test01ClassInit.java"
public class com.ghbs.jvm01.Test01ClassInit
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #6.#24 // java/lang/Object."<init>":()V
#2 = Fieldref #25.#26 // java/lang/System.out:Ljava/io/PrintStream;
#3 = Fieldref #5.#27 // com/ghbs/jvm01/Test01ClassInit.number:I
#4 = Methodref #28.#29 // java/io/PrintStream.println:(I)V
#5 = Class #30 // com/ghbs/jvm01/Test01ClassInit
#6 = Class #31 // java/lang/Object
#7 = Utf8 number
#8 = Utf8 I
#9 = Utf8 <init>
#10 = Utf8 ()V
#11 = Utf8 Code
#12 = Utf8 LineNumberTable
#13 = Utf8 LocalVariableTable
#14 = Utf8 this
#15 = Utf8 Lcom/ghbs/jvm01/Test01ClassInit;
#16 = Utf8 main
#17 = Utf8 ([Ljava/lang/String;)V
#18 = Utf8 args
#19 = Utf8 [Ljava/lang/String;
#20 = Utf8 MethodParameters
#21 = Utf8 <clinit>
#22 = Utf8 SourceFile
#23 = Utf8 Test01ClassInit.java
#24 = NameAndType #9:#10 // "<init>":()V
#25 = Class #32 // java/lang/System
#26 = NameAndType #33:#34 // out:Ljava/io/PrintStream;
#27 = NameAndType #7:#8 // number:I
#28 = Class #35 // java/io/PrintStream
#29 = NameAndType #36:#37 // println:(I)V
#30 = Utf8 com/ghbs/jvm01/Test01ClassInit
#31 = Utf8 java/lang/Object
#32 = Utf8 java/lang/System
#33 = Utf8 out
#34 = Utf8 Ljava/io/PrintStream;
#35 = Utf8 java/io/PrintStream
#36 = Utf8 println
#37 = Utf8 (I)V
{
public com.ghbs.jvm01.Test01ClassInit();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 6: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcom/ghbs/jvm01/Test01ClassInit;
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=1, args_size=1
0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
3: getstatic #3 // Field number:I
6: invokevirtual #4 // Method java/io/PrintStream.println:(I)V
9: return
LineNumberTable:
line 14: 0
line 15: 9
LocalVariableTable:
Start Length Slot Name Signature
0 10 0 args [Ljava/lang/String;
MethodParameters:
Name Flags
args
static {};
descriptor: ()V
flags: ACC_STATIC
Code:
stack=1, locals=0, args_size=0
0: sipush 200
3: putstatic #3 // Field number:I
6: bipush 100
8: putstatic #3 // Field number:I
11: return
LineNumberTable:
line 8: 0
line 11: 6
}
SourceFile: "Test01ClassInit.java"