一、ClassLoader概述
ClassLoader(類加載器)是Java虛擬機的一項核心技術,實現了虛擬機動態加載類及其依賴的特性。ClassLoader主要負責查找並加載類文件,將其轉換成Java類,在需要的時候初始化類,並為Java程序提供必要的運行時環境。在Android系統中,ClassLoader也佔據了同樣的核心位置。
Android應用程序運行在DVM(Dalvik虛擬機)或ART(Android Runtime)上,這兩個運行時環境使用的ClassLoader也不同。DVM使用的ClassLoader是PathClassLoader,它是一種基於Dex文件的類加載器。ART使用的ClassLoader是ArtClassLoader,它是一種基於Oat文件的類加載器。以下我們將以PathClassLoader為例,詳細介紹Android ClassLoader機制的實現原理。
二、ClassLoader分類
ClassLoader的實現機制不一,根據具體的實現方式,可以將ClassLoader分為以下幾種類型:
1. BootStrap ClassLoader
負責加載JVM運行需要的基礎類庫(如rt.jar、i18n.jar、sunrsasign.jar等)。BootStrap ClassLoader是JVM內置的ClassLoader,並不繼承自java.lang.ClassLoader,它是用C++編寫的,提供了JVM的基礎服務。
2. Extension ClassLoader
負責加載JRE擴展的類庫(如jce.jar、localedata.jar等)。Extension ClassLoader是由Sun的擴展類加載器實現的,它繼承自ClassLoader,它的父類加載器是BootStrap ClassLoader。
3. Application ClassLoader
也稱作System ClassLoader,是ClassLoader的默認實現。它負責加載CLASSPATH路徑下的類文件和應用程序本身的類文件。Application ClassLoader是Java應用程序開發中,開發人員最關心的ClassLoader實現之一。
另外的還有自定義ClassLoader,它可以繼承ClassLoader並重新實現加載機制。開發人員可以使用自定義ClassLoader加載特定的類文件或按照特定規則實現類的加載,從而實現一些特定的功能。
三、ClassLoader實現原理
ClassLoader機制的實現依賴於ClassLoader的雙親委派機制。在ClassLoader加載類的時候,首先詢問它的父類加載器是否能夠加載該類,如果不能,則自身嘗試加載。如果自身不能加載,則逐級向上委派,直至BootStrap ClassLoader都不能加載,最終由自身去完成類的加載。這種方式保證了一個類在虛擬機中只有一個版本(不同的ClassLoader實例會加載不同的版本,無法互相訪問),避免了類的重複加載,也保證了類的安全性。
ClassLoader機制的實現過程大致可以分為以下幾個步驟:
1. 對於需要加載的類,首先從緩存中查找是否已經加載過。
ClassLoader會將已經加載成功的類文件緩存在內存中,以便下次使用時可以直接從緩存中獲取。如果已經存在,則返回成功加載的類對象。
2. 如果沒有找到可用的緩存,則請求父ClassLoader進行加載。
ClassLoader在委託父ClassLoader之前,先使用findLoadedClass()方法再進行一次查找,以避免出現重複加載的現象。如果父ClassLoader存在,則請求父ClassLoader進行加載。如果父ClassLoader也無法加載,則繼續向上委派,直至委派到BootStrap ClassLoader。
3. 如果所有的父ClassLoader都無法加載,則從本地文件系統或者網絡等位置獲取要加載的.class文件。
ClassLoader在查找.class文件的時候,會根據一定規則查找指定路徑下的.class文件。一般包括CLASSPATH路徑、系統默認路徑以及其他用戶自定義的路徑。如果能夠查找到要加載的.class文件,則將它轉換成Java類並返回。否則,拋出ClassNotFoundException。
4. 將加載成功的類文件存儲到緩存中。
為了以後可以重用已經成功加載的類文件,ClassLoader會將成功加載的類文件存儲到緩存中,下次再使用時可以直接從緩存中獲取。
四、Class對象與ClassLoader
每個Java類在虛擬機中都有一個對應的Class對象,ClassLoader負責將類文件加載到虛擬機中,並轉換成Class對象。通過Class對象,可以獲取類的信息,包括類名、父類、接口、字段、方法等信息。ClassLoader不僅加載類文件,它還負責創建和管理Class對象,即ClassLoader與Class對象是一一對應的關係。ClassLoader利用類的全限定名來查找並加載類,因此不同的ClassLoader實例可以加載同名的類,但這些類之間是互不共享的,可以看成具有相同類名但具有不同命名空間的不同類。
以下是一個簡單的例子,介紹ClassLoader和Class對象的關係:
public class Main { public static void main(String[] args) throws ClassNotFoundException { // 獲取系統ClassLoader ClassLoader cl = ClassLoader.getSystemClassLoader(); // 加載Hello類 Class c = cl.loadClass("com.example.Hello"); // 輸出Hello的類名 System.out.println("類名:" + c.getName()); // 輸出Hello的Class對象的ClassLoader System.out.println("ClassLoader:" + c.getClassLoader()); } }
以上代碼中,使用ClassLoader.getSystemClassLoader()方法獲取系統ClassLoader,然後使用ClassLoader加載指定的類。最後輸出類的名稱和ClassLoader。在Java中,類的名稱是唯一的,而ClassLoader是有繼承鏈的。因此在ClassLoader的繼承鏈上,可以有多個ClassLoader實例同時加載同名的類。另外,需要注意,系統ClassLoader是默認的ClassLoader,如果沒有指定ClassLoader,一般都會使用系統ClassLoader。
五、注意事項
ClassLoader機制對程序的運行維護起到了重要的作用,但是在使用時還需要注意以下事項:
1. 類的可訪問性
當ClassLoader加載一個類時,要保證該類的訪問性。如果類文件的可訪問性限制了訪問該類的ClassLoader的話,則會拋出SecurityException異常。
2. 類的初始化時機
一個類只有被使用時才會被初始化。類的初始化是指虛擬機為該類生成Class對象,並且在內存中分配資源。類的初始化時機包括:創建該類的實例對象、訪問類的靜態成員(被final修飾的靜態成員除外)、使用是客戶端JVM所在的Java程序啟動時被標明為啟動類的類、使用了反射API的類等。如果一個類沒有被使用,則不會被加載和初始化,即便ClassLoader已經加載了該類的字節碼文件。
3. 線程上下文ClassLoader
線程上下文ClassLoader用於在多個線程中對ClassLoader進行傳遞和繼承。線程的上下文ClassLoader可以通過Thread類的setContextClassLoader()方法設置。當一個線程需要加載一個類時,它首先嘗試使用自己的ClassLoader,如果未找到,則使用其線程上下文ClassLoader。如果該線程上下文ClassLoader也未找到,則委託父線程的上下文ClassLoader進行查找,直至BootStrap ClassLoader。線程上下文ClassLoader基本上是由應用程序自行負責管理的,JVM在不同的線程中使用不同的ClassLoader對同一類進行加載時,可能會導致出現問題。因此,在引入第三方類庫、動態加載類和OSGi等場景下,都需要注意ClassLoader的使用。
六、總結
ClassLoader是Java虛擬機的基礎服務,它提供了Java程序動態加載類的能力。Android應用程序同樣運用了ClassLoader機制,用於實現在運行時動態加載和卸載類。ClassLoader加載類的過程遵循雙親委派機制,保證了類在虛擬機中的唯一性和安全性。ClassLoader機制是Java和Android程序設計不可或缺的一部分,熟練掌握ClassLoader的使用,對程序的設計和性能優化有很大的幫助。
原創文章,作者:CTJE,如若轉載,請註明出處:https://www.506064.com/zh-hant/n/145925.html