????????在Java開發中,理解JVM(Java虛擬機)和類加載機制是掌握高級特性的關鍵。本文將從JDK、JRE、JVM的關系入手,深入講解JVM的內存結構,并詳細剖析類加載的全過程,包括加載時機、流程以及核心機制——雙親委托模型。
一、JDK、JRE、JVM的關系
1.1 三者的核心區別
- JDK(Java Development Kit):Java開發工具包,包含編譯器(
javac
)、調試器(jdb
)等開發工具,是開發Java程序的必備環境。 - JRE(Java Runtime Environment):Java運行時環境,包含JVM和Java類庫,用于運行Java程序。
- JVM(Java Virtual Machine):Java虛擬機,是JRE的核心組件,負責執行字節碼(
.class
文件),并提供內存管理、垃圾回收等功能。
關系圖:
1.2 Java程序跨平臺原理
二、JVM內存結構詳解
????????JVM(Java Virtual Machine) 是Java平臺的核心組件,它提供了跨平臺的 能力,使得Java程序可以在不同的操作系統上運行。JDK中的JVM負責解釋和執 行Java字節碼文件,同時還提供了內存管理、垃圾回收等功能,使得Java程序能 夠高效、安全地運行。
JVM的內存結構是Java程序運行的基石,主要分為以下幾部分:
類加載器(Class Loader):類加載器負責加載Java字節碼文件(.class文件), 并將其轉換為可執行的代碼。它將類加載到JVM的運行時數據區域中,并解析類 的依賴關系
運行時數據區(Runtime Data Area):運行時數據區域是JVM用于存儲程序運行 時數據的區域。它包括以下幾個部分:
2.1 核心區域劃分
區域 | 作用 | 特點 |
---|---|---|
方法區(Method Area) | 存儲類的元數據(如類結構、常量池、靜態變量) | 線程共享,可能存在性能瓶頸(如頻繁GC) |
堆(Heap) | 存放對象實例和數組 | 最大的內存區域,垃圾回收的主要場所 |
棧(Stack) | 存儲局部變量、方法調用棧幀 | 線程私有,生命周期與線程一致 |
本地方法棧(Native Method Stack) | 支持本地方法(如C/C++代碼)調用 | 與JVM棧類似,但服務對象不同 |
程序計數器(Program Counter) | 記錄當前線程執行的字節碼指令地址 | 線程私有,唯一不會拋出OOM異常的區域 |
2.2 執行引擎與垃圾回收
- 執行引擎::執行引擎負責執行編譯后的字節碼指令,將其 轉換為機器碼并執行。它包括解釋器和即時編譯器(Just-In-Time Compiler, JIT)兩個部分,用于提高程序的執行效率。
- 垃圾回收器(GC)::垃圾回收器負責自動回收不再使用的對象和 釋放內存空間。它通過標記-清除、復制、標記-整理等算法來進行垃圾回收
- 本地方法接口(Native Method Interface):本地方法接口允許Java程序調用本 地方法,即使用其他語言編寫的代碼。
三、類加載機制詳解
類加載是Java動態性的核心,JVM通過類加載器將.class
文件加載到內存,并完成初始化。
JVM架構及執行流程如下:
解釋執行:
????????class文件內容,需要交給JVM進行解釋執行,簡單理解就是JVM解釋一行就 執行一行代碼。所以如果Java代碼全是這樣的運行方式的話,效率會稍低一 些。
JIT(Just In Time)即時編譯:
????????執行代碼的另一種方式,JVM可以把Java中的 熱點代碼直接編譯成計算機可 以運行的二進制指令,這樣后續再調用這個熱點代碼的時候,就可以直接運 行編譯好的指令,大大提高運行效率。
3.1 類加載器
類加載器可以將編譯得到的 .class文件 (存儲在磁盤上的物理文件)加載在 到內存中。
3.2 加載時機
當第一次使用到某個類時,該類的class文件會被加載到內存方法區。
3.3 加載過程
類加載的過程:加載、驗證、準備、解析、初始化
具體加載步驟:
注意事項:
????????類加載過程是按需進行的,即在首次使用類時才會觸發類的加載和初始化。此 外,類加載過程是由Java虛擬機的類加載器負責完成的,不同的類加載器可能有 不同的加載策略和行為。
類加載小節:
????????JVM的類加載過程包括加載、驗證、準備、解析和初始化等階段,它們共同完 成將Java類加載到內存中,并為類的靜態變量分配內存、解析符號引用、執行靜 態代碼塊等操作,最終使得類可以被正確地使用和執行。
3.4 加載器分類
JDK8類加載器可以分為以下四類:
3.5?雙親委托機制(Parent Delegation Model)
????????雙親委托機制是Java類加載器的一種工作機制,通過層級加載和委托父類加載器 來加載類,確保類的唯一性、安全性和模塊化。在學習這個知識點之前,大家看 下面題目。
1)問題引入
????????用戶自定義類java.lang.String,在測試類main方法中使用該類,思考: 類加載器到底加載是哪個類,是JDK提供的String,還是用戶自定義的String?
自定義String類:
package java.lang;import java.util.Arrays;public class String {private char[] arr;public String() {System.out.println("in String() ...");arr = new char[10];}public String(char[] array) {System.out.println("in String(char[]) ...");int len = array.length;arr = new char[len];System.arraycopy(array, 0, arr, 0, len);}@Overridepublic String toString() {return "MyString: " + Arrays.toString(arr);}
}
測試類:
import java.lang.String;public class Test035_String {public static void main(String[] args) {String s1 = new String();System.out.println("s1: " + s1);}}
從運行效果可知:最終加載的類是JDK提供的java.lang.String
為什么?答案是雙親委托機制!
2)雙親委托機制
????????如果一個類加載器收到類加載請求,它并不會自己先去加載,而是把這個請求 委托給父類的加載器去執行,如果父類加載器還存在其父類加載器,則進一步向 上委托,最終加載請求會到達頂層的啟動類加載器 Bootstrap ClassLoader 。
????????如果頂層類加載器可以完成加載任務,則進行class文件類加載,加載成功后返 回。如果當前類加載器無法加載,則向下委托給子類加載器,此時子類加載器才 會嘗試加載,成功則返回,失敗則繼續往下委托,如果所有的加載器都無法加載 該類,則會拋出ClassNotFoundException,這就是雙親委托機制。
3.6 常用方法
案例展示:
????????準備一個jdbc的配置文件 jdbc.properties ,借助類加載器中方法解析,遍 歷輸出其配置內容。
配置文件 jdbc.properties :
driverClass=com.mysql.jdbc.Driverurl=jdbc:mysql://localhost:3306/db01username=rootpassword=briup
測試類:
import java.io.IOException;import java.io.InputStream;import java.util.Map.Entry;import java.util.Properties;import java.util.Set;// static ClassLoader getSystemClassLoader()
獲取系統類加載器
// InputStream getResourceAsStream(String name) 加載當前類class
文件相同目錄下資源文件
public class Test036_LoadFile {public static void main(String[] args) throws IOException
{//1.獲取系統類加載器ClassLoader systemClassLoader =
ClassLoader.getSystemClassLoader();//2.利用加載器去加載一個指定的文件// 參數:文件的路徑(注意,該路徑為相對路徑,相對于當前類
class文件存在的目錄)// 返回值:字節流InputStream is =
systemClassLoader.getResourceAsStream("jdbc.properties");System.out.println("is: " + is);//3.實例化Properties對象,解析配置文件內容并輸出Properties prop = new Properties();prop.load(is);//配置文件內容遍歷Set<Entry<Object, Object>> entrySet = prop.entrySet();for (Entry<Object, Object> entry : entrySet) {String key = (String) entry.getKey();String value = (String) entry.getValue();System.out.println(key + ": " + value);}//4.關閉流is.close();}}
運行效果:
注意事項:getResourceAsStream(String path),參數path是相對路徑,相對當前 測試類class文件所在的目錄!
總結
????????本文從JDK/JRE/JVM的關系入手,深入解析了JVM的內存結構和類加載機制,重點講解了雙親委托模型的設計原理和作用。理解這些內容有助于優化程序性能、排查類加載沖突問題,并為后續學習反射、動態代理等高級特性打下基礎。