目錄
6.5. 類加載器-雙親委派機制
6.5.1. 雙親委派機制-作用
6.5.2. 雙親委派機制-工作流程
6.5.3. 雙親委派機制-父加載器
6.5.4. 雙親委派機制-面試題
6.5.5. 雙親委派機制-代碼主動加載一個類
6.6. 類加載器-打破雙親委派機制
6.6.1. 打破委派-ClassLoader原理
6.6.2. 打破委派-打破的場景
6.6.3. 打破委派-自定義類加載器
6.6.4. 打破委派-線程上下文類加載器
6.6.5. 打破委派-OSGi模塊化
6.7. 類加載器-JDK8前后的類加載器
6.5. 類加載器-雙親委派機制
6.5.1. 雙親委派機制-作用
- 雙親委派機制的作用:解決誰來加載類的問題
-
- 保證類加載的安全性:避免惡意代碼替換JDK中的核心類庫(比如:java.lang.String),確保核心類庫的完整性和安全性
- 避免類的重復加載:避免一個類被反復加載
6.5.2. 雙親委派機制-工作流程
- 雙親委派機制的工作流程:當類加載器接收到加載類的任務時,會自底向上查找父類加載器是否加載過該類,是則結束這個過程,否則自頂向下進行加載該類
-
- 自底向上工作流程:每個類加載器都有父類加載器,在類加載過程中,每個類加載器會檢查自身是否加載了該類,是則直接返回該類,否則將加載請求委派給父類加載器
- 自底向上工作優點:只要一個類加載器加載過該類就會直接返回,避免了重復加載
- 自頂向下工作流程:父類加載器會檢查當前類是否在自己的加載目錄中,是則加載后返回類對象,否則交給子類去加載
- 自頂向下的優點:實現從上到下的加載優先級
6.5.3. 雙親委派機制-父加載器
- 父加載器辨析:每個Java實現的類加載器中保存了一個成員變量叫"父"(parent)類加載器,可以理解為它的上級,但不是繼承關系
-
- 應用程序類加載器的父類加載器:擴展類加載器
- 擴展類加載器的父類加載器:null,啟動類加載器是JVM源碼中的,Java代碼是獲取不到的,但是代碼邏輯上啟動類加載器的父類加載器還是啟動類加載器
- 啟動類加載器的父類加載器:啟動類加載器是用C++編寫的沒有父類加載器
6.5.4. 雙親委派機制-面試題
-
- 啟動類加載器加載,根據雙親委派機制,它的優先級是最高的
-
- 不能,因為啟動類加載器在程序啟動時已經加載了JDK提供的String類,接收到委派請求時會直接返回加載好的String類
6.5.5. 雙親委派機制-代碼主動加載一個類
- 使用Java代碼可以主動加載一個類,有兩種實現方式:
-
- 調用Class.forName方法使當前類的類加載器去加載該類
- 調用getClassLoader方法獲取當前類的類加載器,再用類加載器的loadClass方法讓該類加載器加載指定的方法
6.6. 類加載器-打破雙親委派機制
6.6.1. 打破委派-ClassLoader原理
- ClassLoader的原理就存在于四個核心方法中
- loadClass:
-
- 作用:類加載的入口,內部實現雙親委派機制,內部會調用findClass
- 代碼邏輯:
-
- 使用synchronized加鎖,防止多線程情況下出現類被多次加載
- 使用findLoadClass方法判斷這個類是否被當前類加載器加載過
-
-
- 未被加載過,則判斷父類加載器是否為空
-
-
-
-
- 不為空,則由父類加載器再判斷是否加載過這個類,依次類推
- 為空,則由啟動類加載器去加載
-
-
-
-
- 已被加載過,則直接返回這個類
-
-
- 執行完向上委派動作,但類任未被加載,則由當前類加載器加載
-
-
- 使用URLClassLoader的findClass方法獲取特定目錄下的Class字節碼文件,獲取文件對象
-
-
- 調用重載方法傳入resolve=false,不執行連接的過程
- findClass:由類加載器子類實現,獲取二進制數據時調用defineClass,比如URLClassLoader會根據文件路徑去獲取類文件的二進制數據
- defineClass:對類名進行校驗,調用JVM方法將字節碼信息加載到JVM運行時數據區
- resolveClass:執行類生命周期中的連接階段
6.6.2. 打破委派-打破的場景
- Tomcat打破雙親委派機制的原因:Tomcat程序中運行多個Web應用時,如果Web應用中出現相同限定名的類,Tomcat要保證這些類能夠正常加載并運行且保證他們是不同的類,Tomcat就需要打破雙親委派機制
- Tomcat打破雙親委派機制的方式:Tomcat為每一個Web應用提供了自定義類加載器來加載對應的類,這樣就實現了應用之間的類的隔離
6.6.3. 打破委派-自定義類加載器
- 自定義類加載器打破雙親委派機制的方法:復寫ClassLoader中的loadClass方法
- 常見問題:
-
- 要加載的類名如果是以java.開頭,則會拋出安全性異常
- 加載自定義的類都會有一個共同
的父類Object,需要在代碼中交由父類加載器去加載 - 自定義類加載器不手動指定parent會默認指定應用類加載
- 兩個自定義類加載器加載同一個類會被認為是兩個對象,只有相同的類加載器+想通的類限定名才會被認為是一個對象
6.6.4. 打破委派-線程上下文類加載器
- 線程上下文類加載器應用場景:JDBC(為例),JNDI
- JDBC概述:JDBC提供DriverManager來管理jar包中引入的數據庫驅動,這樣就能在Java中操作不同的數據庫
- JDBC打破委派困境:DriverManager位于rt.jar包中,由啟動類加載器加載,但依賴中的MySQL驅動實現類需要應用類加載器去加載
- JDBC-SPI機制:SPI(Service Provider Interface)JDK內置服務發現機制,通過SPI快速找到Driver接口的實現類,類似Spring中的依賴注入
- JDBC工作原理:MySQL為例
-
- 啟動類加載器加載位于rt.jar包的DriverManager
- 初始化DriverManager時,通過SPI機制加載jar包中的數據庫驅動實現類,將此實現類注冊到DriverManager中交由他管理
-
-
- DriverManager利用SPI機制會去加載META-INF/services路徑下的java.sql.Driver文件,將MySQL實現了Driver接口的驅動實現類的全限定名寫入文件就可以被加載并管理
-
-
- SPI中利用了線程上下文類加載器(一般是應用類加載器)去加載獲取的Driver驅動類并創建對象
- SPI中利用了線程上下文類加載器(一般是應用類加載器)去加載獲取的Driver驅動類并創建對象
-
-
- DriverManager使用ServiceLoader去加載Driver實現類,ServiceLoader中獲取線程上下文類加載器去加載實現類
-
-
- 這種由啟動類加載器加載的類去委派應用程序類加載器去加載類的方式打破了雙親委派機制
- 但是JDBC案例中都使用的是JDK提供的類加載器還是會走雙親委派流程,并沒有重寫loadClass,也可以說沒有打破雙親委派機制
6.6.5. 打破委派-OSGi模塊化
- OSGi作用:OSGi是模塊化框架,解決早起JDK所有核心類都放在rt.包下難以管理的問題,它實現同級之間的類加載器委托加載.還使用類加載器實現了熱部署功能
6.7. 類加載器-JDK8前后的類加載器
- JDK8之前版本:擴展類加載器和應用程序類加載器的源碼都位于rt.jar包下的sun.misc.Launcher.java中,這兩個加載器都實現了URLClassLoader,也可以說JDK8之前類加載器是按照類的位置去加載的
- JDK8之后版本:JDK9引入了module概念,類不在放在jar包中加載,而是放在一個個jmod文件中,從jmod文件中加載文件,類加載器發生很多變化:
-
- 啟動類加載器由Java編寫,位于jdk.internal.loader.ClassLoader類中
- 啟動類加載器由Java編寫,位于jdk.internal.loader.ClassLoader類中
-
-
- Java中的BootClassLoader繼承自BuiltinClassLoader實現從模塊中找到要加載的字節碼文件
- 啟動類加載器依然無法通過Java代碼找到保持了統一
-
-
- 擴展類加載器被替換成了平臺類加載器(Platform Class Loader)
- 擴展類加載器被替換成了平臺類加載器(Platform Class Loader)
-
-
- 平臺類加載器遵循模塊化方式加載字節碼文件,繼承關系從URLClassLoader變為BuiltinClassLoader,BuiltinClassLoader實現了從模塊中加載字節碼文件,平臺類加載器的存在更多是為老板的設計方案兼容,自己沒有特殊邏輯
-