在Java中,類和類加載器是密切相關的兩個概念,理解它們有助于我們更好地掌握Java的運行機制。
什么是Java類?
Java類就像是一個模板或藍圖,它定義了對象的屬性和行為。比如"汽車"可以看作一個類,它有顏色、品牌等屬性,有行駛、剎車等行為。我們根據這個類可以創建具體的汽車對象(如一輛紅色的特斯拉)。
// 汽車類(模板)
class Car {String color; // 屬性String brand; // 屬性// 行為void drive() {System.out.println(brand + "汽車正在行駛");}
}// 創建對象(根據模板造具體的車)
Car myCar = new Car();
myCar.color = "紅色";
myCar.brand = "特斯拉";
myCar.drive(); // 輸出:特斯拉汽車正在行駛
什么是類加載器?
類加載器(ClassLoader)就像是"類的搬運工",它的作用是把.class文件(編譯后的類)加載到Java虛擬機(JVM)中,讓JVM能夠認識這個類并使用它。
想象一下:JVM就像一個大型工廠,類就像是生產所需的圖紙。類加載器的工作就是把這些圖紙從文件系統、網絡或其他地方運送到工廠里,供工廠使用。
咱們可以把Java虛擬機(JVM)想象成一個“大工廠”,類加載器就是負責給這個工廠“運圖紙(類)”的工人。
從虛擬機角度看(兩種類加載器)
- 啟動類加載器(Bootstrap ClassLoader):它是工廠里“最核心的元老級搬運工”,用C++寫的,是虛擬機本身的一部分。專門搬最基礎、最核心的“工廠自帶圖紙”,像
java.lang.String
這類Java最根本的類,就靠它搬。 - 其他類加載器:這些是“后來的搬運工”,用Java寫的,和虛擬機是分開的。它們都得繼承
java.lang.ClassLoader
這個抽象類,負責搬一些額外的圖紙。
從開發者角度看(更細致的劃分)
- 啟動類加載器:還是那個“核心元老搬運工”,負責搬
<JRE_HOME>\lib
目錄里(或者-Xbootclasspath
參數指定路徑里)、虛擬機能認出來的“核心圖紙”。比如java.util
(工具類)、java.io
(輸入輸出類)、java.lang
(語言基礎類)這些常用基礎類庫。而且它很“傲嬌”,只認文件名,像rt.jar
這類符合名字的才搬,名字不對的,哪怕在lib
目錄里也不管。另外,Java程序沒法直接調用它。
- 擴展類加載器(Extension ClassLoader):它是“擴展搬運工”,負責搬
<JRE_HOME>/lib/ext
目錄或者java.ext.dir
系統變量指定路徑里的“擴展圖紙”。像swing
(界面相關)、內置js
引擎、xml
解析器這些以javax
開頭的擴展類庫,都由它來搬。開發者是可以直接用這個加載器的。
- 應用程序類加載器(Application ClassLoader):也叫“系統類加載器”,是“用戶搬運工”。負責搬用戶自己指定的類路徑(ClassPath)里的“圖紙”,比如我們自己寫的類,或者第三方的
jar
包。如果我們沒自己定義類加載器,程序默認就用它來搬圖紙。而且它可以通過ClassLoader
的getSystemClassLoader()
方法獲取到,開發者能直接使用。
類與類加載器的關系
- 每個類被加載到JVM后,都會記錄是被哪個類加載器加載的
- 類加載器之間存在"父子關系":Application的父是Extension,Extension的父是Bootstrap
- 判斷兩個類是否相同,不僅要看類名是否一樣,還要看它們是否被同一個類加載器加載
打個比方:兩個名字相同的"汽車"圖紙,如果一個是由"中國搬運工"搬來的,一個是由"美國搬運工"搬來的,JVM會認為它們是不同的類。
為什么需要類加載器?
- 實現了類的動態加載:需要用某個類時才加載,不用時不加載,節省資源
- 實現了隔離性:不同的類加載器可以加載同名類而不沖突,這對容器(如Tomcat)很重要
- 安全性:可以通過自定義類加載器來控制哪些類能被加載,防止惡意代碼
理解類和類加載器的關系,有助于我們解決類沖突、類找不到等問題,也是學習Java反射、動態代理等高級特性的基礎。
在Java類加載機制中,雙親委派模型可以類比成一個公司的文件審批流程,這樣能更通俗易懂地理解。
什么是雙親委派模型
在Java里,類加載器之間存在層次關系,形成了一個類似樹形的結構。除了最頂層的啟動類加載器 ,其他類加載器都有自己的“父加載器”,比如擴展類加載器的父加載器是啟動類加載器,應用程序類加載器的父加載器是擴展類加載器。雙親委派模型規定,當一個類加載器收到類加載請求時,它首先不會自己去嘗試加載這個類,而是把請求委派給父加載器去完成,只有當父加載器無法完成加載任務時,子加載器才會嘗試自己去加載。
用公司審批流程舉例說明
假設你在公司里寫了一份重要的報告,需要經過審批才能正式發布。這就類似于類加載器收到加載類的請求。
- 部門經理(應用程序類加載器):你作為普通員工,寫好報告后會先交給部門經理,對應應用程序類加載器收到類加載請求,它不會自己先去處理,而是把請求向上提交。
- 部門總監(擴展類加載器):部門經理拿到你的報告后,不會直接審批,而是交給部門總監,這就好比應用程序類加載器把加載請求交給擴展類加載器。部門總監拿到報告后,同樣也不會直接審批,而是繼續往上提交。
- 公司老板(啟動類加載器):部門總監把報告交給公司老板,啟動類加載器作為最頂層,先檢查這個報告(類)是不是屬于它負責的核心內容,比如公司的基本規章制度、財務審批流程等相關文件(Java核心類庫) 。如果是,老板就直接審批(加載類);如果不是,比如是關于某個項目的具體報告,老板就會把報告打回給部門總監,說“這事兒不歸我管,你處理一下”。
- 部門總監處理:部門總監接到老板打回的報告后,查看是否屬于自己職責范圍內,比如一些技術規范、行業標準相關的報告,如果是就審批(加載類);如果不是,再打回給部門經理。
- 部門經理處理:部門經理接到報告,一看是關于自己部門項目執行情況的報告,就自己審批了(應用程序類加載器自己加載類)。
雙親委派模型的好處
- 避免重復加載:如果父加載器已經加載過這個類,子加載器就不需要再加載了,節省了資源。就像在公司審批流程中,如果高層已經審批過類似的文件,底層就不用重復審批。
- 保證安全性:它確保了Java核心類庫的一致性和安全性。因為核心類庫都是由最頂層的啟動類加載器加載的,防止了用戶自定義的類去替換核心類。比如不會出現有人寫一個假的
java.lang.String
類混入系統,因為啟動類加載器只會加載真正的核心String
類 。
對象的創建過程
咱們把Java中對象的創建過程,比作"蓋房子"的過程,這樣就很好理解了:
- 確定圖紙(類加載檢查)
1.當你說new 房子()
時,JVM首先會檢查"房子"這個類的圖紙(.class文件)有沒有被加載到內存里。如果沒加載,就會讓類加載器去把圖紙搬過來(類加載過程)。
就像蓋房子前,必須先有建筑圖紙,而且圖紙得先拿到工地才行。
- 圈地(分配內存)
圖紙確認后,JVM會在內存里找一塊合適的地方(堆內存),專門用來蓋這個房子(存放對象)。
相當于開發商在空地上圈出一塊地,大小剛好夠蓋這棟房子。
- 地基處理(初始化零值)
圈好地后,JVM會先把這塊地"打掃干凈",給里面的各種屬性(比如房子的面積、房間數)設置默認值(數字0、布爾false、引用null等)。
就像蓋房子前,先把地皮整平,打好地基,確保基礎是合格的。
- 掛門牌(設置對象頭)
JVM會給這個對象加一個"身份證"(對象頭),里面記錄著:這個對象屬于哪個類(對應哪個圖紙)、哈希碼、GC信息等。
相當于給房子掛上門牌,寫明"這是XX小區3號樓",方便后續查找和管理。
- 內部裝修(執行初始化)
最后一步是真正按照圖紙裝修:給屬性賦值(比如面積120平米、3個房間)、執行構造方法里的邏輯(比如安裝門窗、鋪地板)。
到這一步,房子才算真正蓋好,能住人(使用對象)了。
總結一下:從確認圖紙→分配空間→基礎處理→標記身份→詳細裝修,一步不差,一個對象就創建出來了。就像蓋房子一樣,按流程來才能保證最終的"房子"能用、好用。