前置知識
類加載器是通過類的全限定名,來擷取類的二進制位元組流的代碼。
類加載機制:jvm把class資料加載到記憶體,并對資料進行驗證、準備、解析、初始化,最終形成可以被虛拟機使用的java類。
類的預設加載器,通過雙親委派機制進行類的加載。
加載類的加載器和類本身一起确定類的唯一性,若同一個class檔案,被不同的加載器加載,則是不同的類。
雙親委派機制
啟動類加載器:主要負責加載java的核心類庫,/lib目錄下的rt.jar、resources.jar、charsets.jar和class等
擴充類加載器:主要負責加載java擴充包中的類庫,/lib/ext 目錄下的jar包和class檔案
應用類加載器:主要負責加載目前應用的classpath下的所有類
自定義加載器:加載指定路徑的class檔案
當一個加載器收到加載一個類的請求時,會先把該請求委派給自己的父類加載器執行加載,故所有的類加載都會被委派到啟動類加載器中,若父類加載器加載失敗,才會自己嘗試加載。
在classloader類中的loadclass方法中實作
以上源代碼的邏輯為:
先檢查類是否已經加載過
若類還未加載,則檢查父類加載器是否存在,若存在,則調用父類加載器的loadclass方法進行加載;若不存在,則用啟動加載器加載。
若父類加載器加載失敗,則嘗試自己加載,調用自己的加載方法findclass
使類跟類加載器一樣擁有更嚴格的層級關系。如object類,無論在哪裡使用,最終都會被委派給啟動類加載器加載,保證object類在程式的各種類加載器環境中,都是同一個類。
如果沒有使用雙親委派模型,可以由各個類加載器自行加載的話,如果使用者自定義了一個名為java.lang.object類,放在程式的classpath中,那系統中就會出現多個不同的object類,導緻應用程式執行的混亂。
通過構造器注入parent
自定義一個類加載器,繼承抽象類classloader,重寫loadclass方法,使其不進行雙親委派。
loadclass:預設實作的雙親委派機制
findclass:加載類class位元組碼,是目前類加載器的加載方法,若想自定義的類加載器也遵守雙親委派機制,則隻需要重寫findclass方法
defineclass:将類的位元組碼轉換成class對象
1、第1次是jdk1.2之前,那時已經有了類加載器和classloader類,但是不是雙親委派機制的
2、第2次是模型自身的缺陷導緻的,雙親委派機制使得越基礎的類越由上層的加載器進行加載,正常情況下,使用者代碼繼承、調用基礎類,雙親委派機制加載沒有問題。但是若從基礎類中調回使用者代碼,則在上層加載器中,是無法找到下層的應用代碼的,此時就需要破壞雙親委派機制,java中由基礎類調用spi接口的地方都會如此。
spi接口:service provider interface,如jndi、jdbc等。其本質是面向接口程式設計,具體實作或擴充由第三方在應用程式中實作。
在上層類加載器中無法加載到應用程式中實作的具體類,如何解決這個問題?
通過線程上下文類加載器實作contextclassloader。由thread類的setcontextclassloader方法設定,若未設定,則會繼承父線程的類加載器。在應用程式中,若沒有設定過這個值,則預設為應用程式類加載器。
是以,在調用spi接口時,可以通過getcontextclassloader擷取線程上下文加載器,通過應用程式加載器去加載所需的spi服務類。這是一種父類加載器去請求子類加載器完成類加載的過程,破壞了雙親委派機制。如drivermanager中的實作
3、第3次破壞雙親委派機制是為了追求程式的動态性,如代碼的熱替換,程式的熱部署等,即代碼替換或子產品替換後,不需要重新開機即可生效。
這種實作是基于自定義類加載器實作的,此時加載器不再是有上下層級的樹狀結構,而是一個網狀結構,每一個子產品都有一個自定義的加載器,每當要替換掉一個子產品時,就會将子產品和類加載器都一起替換掉,以實作代碼的熱替換。
參考書籍:《深入了解java虛拟機》第3版,作者:周志明