再次深入理解類載入機制(一)
一、類的載入方法
1、ClassLoader的的基本概念:
與c與c++編寫的程式不同,Java程式並不是可執行檔案,而是有許多的類檔案組成,每個檔案對應一個Java類。而且這些類並不是全部裝進記憶體,而是根據程式執行的需要逐步裝載。ClassLoader是JVM的實現的一部分。
2、ClassLoader的載入流程
當執行第一個程式的時候JVM啟動,執行bootstrap classloader,該載入器負責載入核心Java API,然後呼叫ExtClassLoader載入擴充套件API,最後AppClassLoader載入CLASSPATH目錄下面定義的Class。
首先我們應該要知道ClassLoader除了將Class載入到JVM中之外,還有一個重要的作用就是審查每個類應該由誰進行載入,這是一種父類優先的機制。
除了這兩個作用還有就是將Class位元組碼重新解析成JVM統一要求的格式。
下面是ClassLoader類的loadClass方法:
Java程式碼- protected synchronized Class<?> loadClass(String name, boolean resolve)
- throws ClassNotFoundException
- {
- // First, check if the class has already been loaded
- Class c = findLoadedClass(name);
- //該類還沒有被載入,那麼則
- if (c == null) {
- try {
- //如其父類不為空,那麼則使用其父類進行載入(父類委託機制)
- if (parent != null) {
- c = parent.loadClass(name, false);
- }
- //如果其父類為空則使用BootstrapClass進行載入
- else {
- c = findBootstrapClass0(name);
- }
- } catch (ClassNotFoundException e) {
- // If still not found, then invoke findClass in order
- // to find the class.
- //如果此類的父類載入器無法載入該類,那麼有此類載入器的findClass進行類位元組碼 的載入
- c = findClass(name);
- }
- }
- //是否需要解析類,如果resolve=true時,則保證已經裝載,而且已經連線了。resolve=falses時,則僅僅是去裝載這個類,不關心是否連線了,所以此時可能被連線了,也可能沒有被連線
- if (resolve) {
- resolveClass(c);
- }
- return c;
- }
上面的程式碼可以看出,一個類的載入過程使用了一種父類委託的模式,這種模式的好處主要有兩種:
1、可以避免重複載入,當父類已經載入了該類的時候就沒有必要子ClassLoader再載入一次。
2、考慮到安全因素,如果不使用這種委託模式,那麼可以隨時使用自定義的String來動態代替Java核心API中定義的型別,這樣做會存在非常大的安全隱患,而父類委託模式就可以避免這種情況,因為服載入器已經在啟動時被載入,所以,使用者自定義類是無法載入一個自定義的String的。
除了上面的loadClass方法外方法還有幾個比較重要的
Java程式碼- protected final Class<?> defineClass
- protected Class<?> findClass(String name)
- protected final void resolveClass(Class<?> c)
上面的loadClass方法的作用只是啟動類的載入,指定類應該由誰進行載入。而具體的怎麼載入需要用到
defineClass方法:其能夠將byte位元組流解析成JVM能夠識別的Class物件。
然後還有一個findClass方法:
findClass:負責取得需要載入的類的位元組碼,然後可以呼叫definedClass方法生成類的Class物件。
有了這樣兩個方法我們不僅僅可以通過class檔案例項化物件,還可以通過網路接受位元組碼例項化物件。
一般我們配合使用defineClass和findClass 方法,直接覆蓋ClassLoader父類的findClass方法來實現類的載入規則,findClass取得需要載入的類的位元組碼,defineClass根據位元組碼建立類物件
破壞雙親委派模型
雙親委派模型第一次被破壞:
雙親委派模型在jdk1.2的時候才被引入,提供了findClass方法供使用者進行重寫以指定自己的類載入規則,但是在jdk1.2之前使用者繼承ClassLoader的目的就是為了重寫loadClass方法,但是雙親委派的具體邏輯就實現在這個方法之中,所以在JDK1.2之後就不提倡使用者覆蓋loadClass方法,而應當把自己得把類載入邏輯解除安裝findClass方法中,如果父類載入其載入失敗會呼叫自己的findClass進行類的載入,這樣就可以保證心寫出來的類載入器符合雙親委派模型。
雙親委派模型第二次被破壞:
雙親委派模型的第二次破壞是由這個模型本身造成的,這個模型中是越基礎的類越由上層的載入器載入,之所以基礎是因為他們總是被作為使用者呼叫的API但是事無絕對,如果基礎類又要呼叫使用者的程式碼怎麼辦???
一個典型的例子就是JNDI,他的程式碼由啟動類進行載入,但是JNDI的目的是對資源進行集中管理和查詢,他需要調下用呼叫獨立廠商實現並部署在應用程式ClassPath
下的JNDI介面提供者,這個問題的解決是在程式中引入了不太優雅的設計:執行緒上下文類載入器,JNDI可以使用這個執行緒上下文類載入器去載入所需要的SPI,也就是父類載入器請求自類載入器去完成類載入的動作,這已經違反了雙親委派模型的一般性原則,在Java中所有涉及SPI的如jNDI , JDBC , JCE , JAXB和JBI都使用這種模型來進行類的載入。
雙親委派模型第三次被破壞:
這裡主要是說OSGI為了實現Java模組化熱部署而自定義的類載入機制的實現。它把雙親委派模型的樹狀結構改編成了自己的更加複雜的網狀結構,具體的這裡已經暫時超過了小菜的討論範圍