類加載器
兩個類來源于同一個 Class文件,被同一個Java虛擬機加載,只要加載它們的類加載器不同,那這兩個類就必定不相等
這里所指的“相等”,包括代表類的Class對象的equals()方法、isAssignableFrom()方法、isInstance()方法的返回結果,也包括了使用instanceof關鍵字做對象所屬關系判定等各種情況
總結:
類加載器
? 類加載器作用于類加載的加載階段,也就是加載class文件到方法區中,并且在方法區生成Class對象,通過Class對象可以訪問方法區中的類信息。每個Class對象都有一個引用指向加載它的類加載器。
? JVM中內置了三個類加載器,啟動類加載器、擴展類加載器、應用程序類加載器。其中,啟動類加載器用來加載JAVA_HOME/lib
下的核心類庫比如rt.jar
;擴展類加載器用來加載JAVA_HOME/lib/ext
下的類;應用程序類加載器用來加載classpath
下的類。
? 這三個類加載器都繼承自ClassLoad抽象類,類加載器可以通過getParent()
方法來獲取它的父加載器,例如應用程序類加載器的父加載器是擴展類加載器,而擴展類加載器的父加載器是啟動類加載器,只不過擴展類加載器通過getParent()
方法獲得的是null,因為啟動類加載器是基于c++實現的。
? 三個加載器的父子關系不是通過繼承來實現的,而是通過組合方式來實現的。
雙親委派模型:
- 啟動類加載器(Bootstrap Class Loader):是由Hotspot虛擬機提供的類加載器,JDK9之前使用C++編寫的、JDK9之后使用Java編寫。默認加載Java安裝目錄/jre/lib下的類文件,比如rt.jar,tools.jar,resources.jar等。
- 擴展類加載器(Extension Class Loader):默認加載Java安裝目錄/jre/lib/ext下的類文件。(JDK9及以后,被重命名為平臺類加載器Platform ClassLoader)
- 應用程序類加載器(Application Class Loader):負責加載用戶類路徑(ClassPath)上所有的類庫
雙親委派機制指的是:自底向上查找是否加載過,再由頂向下進行加載
? 雙親委派模型:除了頂層的啟動類加載器外,其余的類加載器都應有自己的父類加載器。不過這里類加載器之間的父子關系一般不是以繼承(Inheritance)的關系來實現的,而是通常使用組合 (Composition)關系來復用父加載器的代碼。
? 如果一個類加載器收到了類加載的請求,每個類加載器都會先檢查是否已經加載了該類,如果已經加載則直接返回,否則會將加載請求委派給父類加載器。每一個層次的類加載器都是如此,因此所有的加載請求最終都應該傳送到最頂層的啟動類加載器中,只有當父加載器反饋自己無法完成這個加載請求(它的搜索范圍中沒有找到所需的類)時, 子加載器才會嘗試自己去完成加載。
為什么使用雙親委派:
? 1.保證類加載的安全性,通過雙親委派機制避免惡意代碼替換JDK中的核心類庫,比如java.lang.String,確保核心類庫的完整性和安全性。
? 2.避免重復加載,雙親委派機制可以避免同一個類被多次加載。
為什么要向上委派,不能向下委派?
? 是否能一開始就由啟動類加載器加載所有的類,啟動類加載器發現不在自己加載范圍內的類就交給自己的子類加載器?答案是不行的,因為一個類加載器可以只有一個父類加載器,需要加載類時直接向上委派就行,但如果是向下委派,同時又有多個子類加載器,這時候就不知道要委派給哪個子類加載器的(可能有多個子類加載器的原因是因為可以有自定義類加載器,加載器的組合是多樣的,但不管怎樣,只可能有一個父類加載器),這就好比一個二叉樹,從葉子節點向上走,是有且只有一條唯一的路徑的,而且這條路徑的終點必然是根節點,而從根節點出發,一直向下走,是不能確定一條唯一路徑的,雖然最終能到達某一葉子節點,但具體是哪個葉子節點取決于每次向下走的路線決策。而且向下委派意味著需要修改應用程序類加載器的源碼。
破壞雙親委派模型:
自定義加載器的話,需要繼承 ClassLoader 。如果我們不想打破雙親委派模型,就重寫 ClassLoader 類中的 findClass() 方法即可,無法被父類加載器加載的類最終會通過這個方法被加載。但是,如果想打破雙親委派模型則需要重寫 loadClass() 方法。
Tomcat采用自定義類加載器破壞了雙親委派,實現了Web應用之間的類的隔離
JDBC:
? JDBC中使用了DriverManager來管理項目中引入的不同數據庫的驅動,比如mysql驅動、oracle驅動。
? DriverManager屬于rt.jar是啟動類加載器加載的。而用戶jar包中的驅動需要由應用類加載器加載,這就違反了雙親委派機制。
? DriverManage使用SPI機制,最終加載jar包中對應的驅動類。
? SPI中使用了線程上下文中保存的類加載器進行類的加載,這個類加載器一般是應用程序類加載器。
? 這種由啟動類加載器加載的類,委派應用程序類加載器去加載類的方式,打破了雙親委派機制。
? JDBC接口是jre下的,但實現是第三方供應商提供的,按理來說,一個類及其依賴類由同一個類加載器加載(確保這些類之間的依賴關系正確并保持一致),但這種情況下不會被同一個類加載器加載,這就需要用到線程上下文類加載器解決
? 線程上下文類加載器:當一個線程啟動時,jvm會將應用類加載器賦值給當前線程的線程上下文類加載器,此時父類加載器就可以提高線程上下文類加載器獲取到子類加載器,再由子類加載器加載父類找不到的類
什么時候需要自定義類加載器:
- 想加載非classpath隨意路徑的類文件
- 隔離同名類,需要不同應用下的同名類可以不沖突,如tomcat容器