你真的了解Java類加載機制嗎?
大家好,我是小米,一個喜歡分享技術(shù)的程序員。今天我來給大家簡述一下Java類加載模型。

在Java中,類的加載過程是在程序運行時動態(tài)進行的。Java的類加載模型可以分為三個步驟:加載、連接和初始化。
類加載過程:加載
首先是加載階段,也就是將類的字節(jié)碼加載到內(nèi)存中。在Java中,有三種不同的類加載器:Bootstrap ClassLoader、Extension ClassLoader和Application ClassLoader。
Bootstrap ClassLoader是最頂層的類加載器,負責加載JRE的核心類庫,如java.lang包中的類。
Extension ClassLoader負責加載Java的擴展類庫。
Application ClassLoader則負責加載應(yīng)用程序的類。
類加載過程:連接
接下來是連接階段,也就是將加載的字節(jié)碼轉(zhuǎn)換為可執(zhí)行的代碼。連接階段分為三個步驟:驗證、準備和解析。
在驗證階段,Java會對字節(jié)碼進行驗證,確保其符合Java虛擬機規(guī)范。
在準備階段,Java會為類的靜態(tài)變量分配內(nèi)存,并將其初始化為默認值。
在解析階段,Java會將符號引用解析為直接引用。
類加載過程:初始化
最后是初始化階段,也就是執(zhí)行類的構(gòu)造函數(shù),并執(zhí)行靜態(tài)變量的賦值操作。在Java中,類的初始化是線程安全的,因為只有一個線程會執(zhí)行初始化操作。
什么是雙親委派模型
那么,什么是雙親委派模型呢?簡單來說,就是在類加載時,先由父類加載器去加載,如果父類加載器找不到該類,才由子類加載器去加載。這種模型的好處是可以保證Java程序的安全性和穩(wěn)定性,因為如果父類加載器已經(jīng)加載了一個類,子類加載器就不需要再加載一遍,避免了出現(xiàn)類似于同名類被重復加載的情況。但是,雙親委派模型也有一些缺點,例如無法實現(xiàn)對于同一個類的不同版本的加載,因為父類加載器會優(yōu)先加載已經(jīng)存在的類,導致子類加載器無法加載另一個版本的同名類。
為什么Tomcat要自定義類加載器
那么,為什么Tomcat要自定義類加載器呢?這是因為在Tomcat中,有多個Web應(yīng)用程序需要加載自己的類庫。如果使用Java默認的雙親委派模型,可能會導致同名類庫被多個Web應(yīng)用程序重復加載,浪費內(nèi)存資源。所以,Tomcat使用自定義的類加載器來實現(xiàn)Web應(yīng)用程序之間的隔離,確保每個Web應(yīng)用程序只加載自己的類庫。
案例分析
最后,我用一個電商項目實際的案例來演示自定義類加載器。假設(shè)我們有一個電商項目,其中包含了一個名為“Order”的類,我們需要在項目中同時使用兩個版本的“Order”類,一個是1.0版本,一個是2.0版本。這時候,我們就可以自定義一個類加載器,通過指定不同的類加載路徑,分別加載兩個版本的“Order”類。具體實現(xiàn)代碼如下:

在這個示例中,我們自定義了一個名為CustomClassLoader的類加載器,它接受一個classPath參數(shù),表示要加載類的路徑。在findClass方法中,我們首先通過loadClassData方法讀取字節(jié)碼文件,然后通過defineClass方法將字節(jié)碼文件轉(zhuǎn)換為Class對象返回。
為了演示如何加載兩個版本的同名類,我們可以分別將兩個版本的Order類放置于不同的路徑中,然后分別使用兩個CustomClassLoader實例加載它們。具體示例如下:

這樣,我們就成功地通過自定義類加載器加載了兩個版本的同名類,實現(xiàn)了類的隔離。
自定義類加載器的場景
除了Tomcat這種容器框架需要自定義類加載器之外,還有其他一些場景也可能需要自定義類加載器。下面列舉一些常見的場景:
插件化開發(fā):是一種常見的開發(fā)模式,通過動態(tài)加載插件,可以使應(yīng)用程序具有更強的可擴展性。在插件化開發(fā)中,通常會涉及到加載不同的插件,這就需要使用自定義類加載器來實現(xiàn)不同插件的隔離。自定義類加載器可以使插件之間相互獨立,不會相互影響。
熱部署:在一些特殊場景下,可能需要對應(yīng)用程序進行熱部署,即在應(yīng)用程序運行過程中替換某些類。為了實現(xiàn)熱部署,需要使用自定義類加載器,可以在加載類時重新讀取字節(jié)碼文件,從而實現(xiàn)對類的更新。這種方式可以避免應(yīng)用程序的重啟,提高了應(yīng)用程序的可用性。
多版本控制:在一些應(yīng)用程序中,可能需要同時使用多個版本的同名類。例如,在進行系統(tǒng)升級時,可能需要同時存在新舊兩個版本的類,以保證系統(tǒng)的兼容性。為了實現(xiàn)多版本控制,需要使用自定義類加載器,可以將不同版本的類隔離開來,避免沖突。
實現(xiàn)類似于Java EE容器的類加載器層次結(jié)構(gòu):在一個Web應(yīng)用程序中,可以定義多個類加載器,每個類加載器負責加載特定類型的類,并且它們之間形成了一種父子關(guān)系。這樣可以實現(xiàn)對不同類的隔離和管理。
防止Java反序列化漏洞:Java序列化和反序列化可以用于將Java對象轉(zhuǎn)換為字節(jié)流以及從字節(jié)流中還原Java對象。但是,Java序列化機制存在一些安全漏洞,攻擊者可以通過反序列化來執(zhí)行惡意代碼。為了避免這種情況,可以使用自定義類加載器來限制反序列化操作只在特定的安全上下文中進行。
加載非標準格式的類文件:有時候,我們可能需要加載非標準格式的類文件,如動態(tài)生成的類或嵌入式Java類等。這些類文件可能無法被標準的類加載器加載,這時候就需要自定義類加載器來加載這些類文件。
總之,自定義類加載器是一種非常有用的工具,可以在某些特殊場景下實現(xiàn)類的隔離和動態(tài)加載。雖然自定義類加載器的使用比較復雜,但只要掌握了其原理和使用方法,就可以為我們的應(yīng)用程序帶來更多的可能性。
END
如有疑問或者更多的技術(shù)分享,歡迎關(guān)注我的微信公眾號“知其然亦知其所以然”!

