寫在前面:Java
中的所有類,必須被裝載到jvm
中才能執行,這個裝載工作是由jvm
中的類裝載器完成的,類裝載器所做的工作實質是把類檔案從硬碟讀取到記憶體中,JVM
在載入類的時候,都是通過ClassLoader
的loadClass()
方法來載入class的,loadClass
使用雙親委派模式。
為了更好的理解類的載入機制,我們來深入研究一下ClassLoader
和他的loadClass()
方法。
原始碼分析
1 |
public abstract class ClassLoader |
ClassLoader
類是一個抽象類,sun公司是這麼解釋這個類的:
1 2 3 4 5 6 7 |
/** * A class loader is an object that is responsible for loading classes. The * class ClassLoader is an abstract class. Given the binary name of a class, a class loader should attempt to * locate or generate data that constitutes a definition for the class. A * typical strategy is to transform the name into a file name and then read a * "class file" of that name from a file system. **/ |
大致意思如下:
class loader是一個負責載入classes的物件,ClassLoader類是一個抽象類,需要給出類的二進位制名稱,class loader嘗試定位或者產生一個class的資料,一個典型的策略是把二進位制名字轉換成檔名然後到檔案系統中找到該檔案。
接下來我們看loadClass方法的實現方式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
protected Class> loadClass(String name, boolean resolve) throws ClassNotFoundException { synchronized (getClassLoadingLock(name)) { // First, check if the class has already been loaded Class c = findLoadedClass(name); if (c == null) { long t0 = System.nanoTime(); try { if (parent != null) { c = parent.loadClass(name, false); } else { c = findBootstrapClassOrNull(name); } } catch (ClassNotFoundException e) { // ClassNotFoundException thrown if class not found // from the non-null parent class loader } if (c == null) { // If still not found, then invoke findClass in order // to find the class. long t1 = System.nanoTime(); c = findClass(name); // this is the defining class loader; record the stats sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0); sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1); sun.misc.PerfCounter.getFindClasses().increment(); } } if (resolve) { resolveClass(c); } return c; } } |
還是來看sun公司對該方法的解釋:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
/** * Loads the class with the specified binary name. The * default implementation of this method searches for classes in the * following order: * * * * Invoke {<a href="http://www.jobbole.com/members/57845349">@link</a> #findLoadedClass(String)} to check if the class * has already been loaded. * * Invoke the {<a href="http://www.jobbole.com/members/57845349">@link</a> #loadClass(String) loadClass} method * on the parent class loader. If the parent is null the class * loader built-in to the virtual machine is used, instead. * * Invoke the {<a href="http://www.jobbole.com/members/57845349">@link</a> #findClass(String)} method to find the * class. * * * * If the class was found using the above steps, and the * resolve flag is true, this method will then invoke the {<a href="http://www.jobbole.com/members/57845349">@link</a> * #resolveClass(Class)} method on the resulting Class object. * * Subclasses of ClassLoader are encouraged to override {<a href="http://www.jobbole.com/members/57845349">@link</a> * #findClass(String)}, rather than this method. * * Unless overridden, this method synchronizes on the result of * {<a href="http://www.jobbole.com/members/57845349">@link</a> #getClassLoadingLock getClassLoadingLock} method * during the entire class loading process. * */ |
大致內容如下:
使用指定的二進位制名稱來載入類,這個方法的預設實現按照以下順序查詢類: 呼叫
findLoadedClass(String)
方法檢查這個類是否被載入過 使用父載入器呼叫loadClass(String)
方法,如果父載入器為Null
,類載入器裝載虛擬機器內建的載入器呼叫findClass(String)
方法裝載類, 如果,按照以上的步驟成功的找到對應的類,並且該方法接收的resolve
引數的值為true
,那麼就呼叫resolveClass(Class)
方法來處理類。ClassLoader
的子類最好覆蓋findClass(String)
而不是這個方法。 除非被重寫,這個方法預設在整個裝載過程中都是同步
的(執行緒安全
的)
接下來,我們開始分析該方法。
**protected Class> loadClass(String name, boolean resolve)** 該方法的訪問控制符是protected
,也就是說該方法**同包內和派生類中可用** 返回值型別Class
>
,這裡用到**泛型**。這裡使用萬用字元
?作為泛型實參表示物件可以 接受任何型別(類型別)。因為該方法不知道要載入的類到底是什麼類,所以就用了通用的泛型。
String name要查詢的類的名字,
boolean resolve,一個標誌,
true表示將呼叫
resolveClass(c)處理該類
throws ClassNotFoundException 該方法會丟擲找不到該類的異常,這是一個非執行時異常
synchronized (getClassLoadingLock(name)) 看到這行程式碼,我們能知道的是,這是一個同步程式碼塊,那麼synchronized的括號中放的應該是一個物件。我們來看getClassLoadingLock(name)
方法的作用是什麼:
1 2 3 4 5 6 7 8 9 10 11 |
protected Object getClassLoadingLock(String className) { Object lock = this; if (parallelLockMap != null) { Object newLock = new Object(); lock = parallelLockMap.putIfAbsent(className, newLock); if (lock == null) { lock = newLock; } } return lock; } |
以上是getClassLoadingLock(name)
方法的實現細節,我們看到這裡用到變數parallelLockMap
,根據這個變數的值進行不同的操作,如果這個變數是Null,那麼直接返回this,如果這個屬性不為Null,那麼就新建一個物件,然後在呼叫一個putIfAbsent(className, newLock);
方法來給剛剛建立好的物件賦值,這個方法的作用我們一會講。那麼這個parallelLockMap
變數又是哪來的那,我們發現這個變數是ClassLoader
類的成員變數:
1 |
private final ConcurrentHashMap parallelLockMap; |
這個變數的初始化工作在ClassLoader
的建構函式中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
private ClassLoader(Void unused, ClassLoader parent) { this.parent = parent; if (ParallelLoaders.isRegistered(this.getClass())) { parallelLockMap = new ConcurrentHashMap(); package2certs = new ConcurrentHashMap(); domains = Collections.synchronizedSet(new HashSet()); assertionLock = new Object(); } else { // no finer-grained lock; lock on the classloader instance parallelLockMap = null; package2certs = new Hashtable(); domains = new HashSet(); assertionLock = this; } } |
這裡我們可以看到建構函式根據一個屬性ParallelLoaders
的Registered
狀態的不同來給parallelLockMap
賦值。 我去,隱藏的好深,好,我們繼續挖,看看這個ParallelLoaders
又是在哪賦值的呢?我們發現,在ClassLoader類中包含一個靜態內部類private static class ParallelLoaders
,在ClassLoader
被載入的時候這個靜態內部類就被初始化。這個靜態內部類的程式碼我就不貼了,直接告訴大傢什麼意思,sun公司是這麼說的:Encapsulates the set of parallel capable loader types
,意識就是說:封裝了並行的可裝載的型別的集合。
上面這個說的是不是有點亂,那讓我們來整理一下: 首先,在ClassLoader類中有一個靜態內部類
ParallelLoaders
,他會指定的類的並行能力,如果當前的載入器被定位為具有並行能力,那麼他就給parallelLockMap
定義,就是new
一個ConcurrentHashMap()
,那麼這個時候,我們知道如果當前的載入器是具有並行能力的,那麼parallelLockMap
就不是Null
,這個時候,我們判斷parallelLockMap
是不是Null
,如果他是null,說明該載入器沒有註冊並行能力,那麼我們沒有必要給他一個加鎖的物件,getClassLoadingLock
方法直接返回this
,就是當前的載入器的一個例項。如果這個parallelLockMap
不是null
,那就說明該載入器是有並行能力的,那麼就可能有並行情況,那就需要返回一個鎖物件。然後就是建立一個新的Object物件,呼叫parallelLockMap
的putIfAbsent(className, newLock)
方法,這個方法的作用是:首先根據傳進來的className,檢查該名字是否已經關聯了一個value值,如果已經關聯過value值,那麼直接把他關聯的值返回,如果沒有關聯過值的話,那就把我們傳進來的Object物件作為value值,className作為Key值組成一個map返回。然後無論putIfAbsent方法的返回值是什麼,都把它賦值給我們剛剛生成的那個Object物件。 這個時候,我們來簡單說明一下getClassLoadingLock(String className)
的作用,就是: 為類的載入操作返回一個鎖物件。為了向後相容,這個方法這樣實現:如果當前的classloader物件註冊了並行能力,方法返回一個與指定的名字className相關聯的特定物件,否則,直接返回當前的ClassLoader物件。
Class c = findLoadedClass(name); 在這裡,在載入類之前先呼叫findLoadedClass
方法檢查該類是否已經被載入過,findLoadedClass會返回一個Class
型別的物件,如果該類已經被載入過,那麼就可以直接返回該物件(在返回之前會根據resolve
的值來決定是否處理該物件,具體的怎麼處理後面會講)。 如果,該類沒有被載入過,那麼執行以下的載入過程
1 2 3 4 5 6 7 8 9 10 |
try { if (parent != null) { c = parent.loadClass(name, false); } else { c = findBootstrapClassOrNull(name); } } catch (ClassNotFoundException e) { // ClassNotFoundException thrown if class not found // from the non-null parent class loader } |
如果父載入器不為空,那麼呼叫父載入器的loadClass
方法載入類,如果父載入器為空,那麼呼叫虛擬機器的載入器來載入類。
如果以上兩個步驟都沒有成功的載入到類,那麼
1 |
c = findClass(name); |
呼叫自己的findClass(name)
方法來載入類。
這個時候,我們已經得到了載入之後的類,那麼就根據resolve
的值決定是否呼叫resolveClass
方法。resolveClass
方法的作用是:
連結指定的類。這個方法給
Classloader
用來連結一個類,如果這個類已經被連結過了,那麼這個方法只做一個簡單的返回。否則,這個類將被按照Java™
規範中的Execution
描述進行連結……
至此,ClassLoader類以及loadClass方法的原始碼我們已經分析完了,那麼。結合原始碼的分析,我們來總結一下:
總結
java中的類大致分為三種:
1.系統類 2.擴充套件類 3.由程式設計師自定義的類
類裝載方式,有兩種:
1.隱式裝載, 程式在執行過程中當碰到通過new 等方式生成物件時,隱式呼叫類裝載器載入對應的類到jvm中。 2.顯式裝載, 通過class.forname()等方法,顯式載入需要的類
類載入的動態性體現:
一個應用程式總是由n多個類組成,Java程式啟動時,並不是一次把所有的類全部載入後再執行,它總是先把保證程式執行的基礎類一次性載入到jvm中,其它類等到jvm用到的時候再載入,這樣的好處是節省了記憶體的開銷,因為java最早就是為嵌入式系統而設計的,記憶體寶貴,這是一種可以理解的機制,而用到時再載入這也是java動態性的一種體現
java類裝載器
1 2 3 4 5 6 7 8 9 10 11 |
Java中的類裝載器實質上也是類,功能是把類載入jvm中,值得注意的是jvm的類裝載器並不是一個,而是三個,層次結構如下: Bootstrap Loader - 負責載入系統類 | - - ExtClassLoader - 負責載入擴充套件類 | - - AppClassLoader - 負責載入應用類 |
為什麼要有三個類載入器,一方面是分工,各自負責各自的區塊,另一方面為了實現委託模型,下面會談到該模型
類載入器之間是如何協調工作的
前面說了,java中有三個類載入器,問題就來了,碰到一個類需要載入時,它們之間是如何協調工作的,即java是如何區分一個類該由哪個類載入器來完成呢。 在這裡java採用了委託模型機制,這個機制簡單來講,就是“類裝載器有載入類的需求時,會先請示其Parent使用其搜尋路徑幫忙載入,如果Parent 找不到,那麼才由自己依照自己的搜尋路徑搜尋類”
下面舉一個例子來說明,為了更好的理解,先弄清楚幾行程式碼:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
Public class Test{ Public static void main(String[] arg){ ClassLoader c = Test.class.getClassLoader(); //獲取Test類的類載入器 System.out.println(c); ClassLoader c1 = c.getParent(); //獲取c這個類載入器的父類載入器 System.out.println(c1); ClassLoader c2 = c1.getParent();//獲取c1這個類載入器的父類載入器 System.out.println(c2); } } |
執行結果:
1 2 3 4 5 |
……AppClassLoader…… ……ExtClassLoader…… Null |
可以看出Test是由AppClassLoader載入器載入的,AppClassLoader的Parent
載入器是 ExtClassLoader,但是ExtClassLoader
的Parent
為 null
是怎麼回事呵,朋友們留意的話,前面有提到Bootstrap Loader是用C++語言寫的,依java的觀點來看,邏輯上並不存在Bootstrap Loader的類實體,所以在java
程式程式碼裡試圖列印出其內容時,我們就會看到輸出為null
。
類裝載器ClassLoader(一個抽象類)描述一下JVM載入class檔案的原理機制
類裝載器就是尋找類或介面位元組碼檔案進行解析並構造JVM內部物件表示的元件,在java中類裝載器把一個類裝入JVM,經過以下步驟:
1、裝載:查詢和匯入Class檔案 2、連結:其中解析步驟是可以選擇的 (a)檢查:檢查載入的class檔案資料的正確性 (b)準備:給類的靜態變數分配儲存空間 (c)解析:將符號引用轉成直接引用 3、初始化:對靜態變數,靜態程式碼塊執行初始化工作
類裝載工作由ClassLoder
和其子類負責。JVM在執行時會產生三個ClassLoader:根裝載器,ExtClassLoader
(擴充套件類裝載器)和AppClassLoader
,其中根裝載器不是ClassLoader的子類,由C++編寫,因此在java中看不到他,負責裝載JRE的核心類庫,如JRE目錄下的rt.jar,charsets.jar等。ExtClassLoader
是ClassLoder
的子類,負責裝載JRE擴充套件目錄ext下的jar類包;AppClassLoader
負責裝載classpath路徑下的類包,這三個類裝載器存在父子層級關係****,即根裝載器是ExtClassLoader的父裝載器,ExtClassLoader是AppClassLoader的父裝載器。預設情況下使用AppClassLoader裝載應用程式的類
Java裝載類使用“全盤負責委託機制”。“全盤負責”是指當一個ClassLoder
裝載一個類時,除非顯示的使用另外一個ClassLoder
,該類所依賴及引用的類也由這個ClassLoder
載入;“委託機制”是指先委託父類裝載器尋找目標類,只有在找不到的情況下才從自己的類路徑中查詢並裝載目標類。這一點是從安全方面考慮的,試想如果一個人寫了一個惡意的基礎類(如java.lang.String
)並載入到JVM
將會引起嚴重的後果,但有了全盤負責制,java.lang.String
永遠是由根裝載器來裝載,避免以上情況發生 除了JVM預設的三個ClassLoder
以外,第三方可以編寫自己的類裝載器,以實現一些特殊的需求。類檔案被裝載解析後,在JVM
中都有一個對應的java.lang.Class
物件,提供了類結構資訊的描述。陣列,列舉及基本資料型別,甚至void
都擁有對應的Class
物件。Class
類沒有public
的構造方法,Class
物件是在裝載類時由JVM
通過呼叫類裝載器中的defineClass()
方法自動構造的。