Java Classloader機制解析

作Java開發,對於ClassLoader的機制是必需要熟悉的基礎知識,本文針對Java ClassLoader的機制作一個簡要的總結。由於不一樣的JVM的實現不一樣,本文所描述的內容均只限於Hotspot Jvm.java

本文將會從JDK默認的提供的ClassLoader,雙親委託模型,如何自定義ClassLoader以及Java中打破雙親委託機制的場景四個方面入手去討論和總結一下。bootstrap

JDK默認ClassLoader

JDK 默認提供了以下幾種ClassLoaderapi

  1. Bootstrp loader
    Bootstrp加載器是用C++語言寫的,它是在Java虛擬機啓動後初始化的,它主要負責加載%JAVA_HOME%/jre/lib,-Xbootclasspath參數指定的路徑以及%JAVA_HOME%/jre/classes中的類。緩存

  1. ExtClassLoader  
    Bootstrp loader加載ExtClassLoader,而且將ExtClassLoader的父加載器設置爲Bootstrp loader.ExtClassLoader是用Java寫的,具體來講就是 sun.misc.Launcher$ExtClassLoader,ExtClassLoader主要加載%JAVA_HOME%/jre/lib/ext,此路徑下的全部classes目錄以及java.ext.dirs系統變量指定的路徑中類庫。網絡

  2. AppClassLoader 
    Bootstrp loader加載完ExtClassLoader後,就會加載AppClassLoader,而且將AppClassLoader的父加載器指定爲 ExtClassLoader。AppClassLoader也是用Java寫成的,它的實現類是 sun.misc.Launcher$AppClassLoader,另外咱們知道ClassLoader中有個getSystemClassLoader方法,此方法返回的正是AppclassLoader.AppClassLoader主要負責加載classpath所指定的位置的類或者是jar文檔,它也是Java程序默認的類加載器。app

綜上所述,它們之間的關係能夠經過下圖形象的描述:jvm

雙親委託模型

Java中ClassLoader的加載採用了雙親委託機制,採用雙親委託機制加載類的時候採用以下的幾個步驟:ide

  1. 當前ClassLoader首先從本身已經加載的類中查詢是否此類已經加載,若是已經加載則直接返回原來已經加載的類。spa

    每一個類加載器都有本身的加載緩存,當一個類被加載了之後就會放入緩存,等下次加載的時候就能夠直接返回了。.net

  2. 當前classLoader的緩存中沒有找到被加載的類的時候,委託父類加載器去加載,父類加載器採用一樣的策略,首先查看本身的緩存,而後委託父類的父類去加載,一直到bootstrp ClassLoader.

  3. 當全部的父類加載器都沒有加載的時候,再由當前的類加載器加載,並將其放入它本身的緩存中,以便下次有加載請求的時候直接返回。

說到這裏你們可能會想,Java爲何要採用這樣的委託機制?理解這個問題,咱們引入另一個關於Classloader的概念「命名空間」, 它是指要肯定某一個類,須要類的全限定名以及加載此類的ClassLoader來共同肯定。也就是說即便兩個類的全限定名是相同的,可是由於不一樣的 ClassLoader加載了此類,那麼在JVM中它是不一樣的類。明白了命名空間之後,咱們再來看看委託模型。採用了委託模型之後加大了不一樣的 ClassLoader的交互能力,好比上面說的,咱們JDK本生提供的類庫,好比hashmap,linkedlist等等,這些類由bootstrp 類加載器加載了之後,不管你程序中有多少個類加載器,那麼這些類其實都是能夠共享的,這樣就避免了不一樣的類加載器加載了一樣名字的不一樣類之後形成混亂。

如何自定義ClassLoader

Java除了上面所說的默認提供的classloader之外,它還允許應用程序能夠自定義classloader,那麼要想自定義classloader咱們須要經過繼承java.lang.ClassLoader來實現,接下來咱們就來看看再自定義Classloader的時候,咱們須要注意的幾個重要的方法:

1.loadClass 方法

loadClass method declare

 

public Class<?> loadClass(String name)  throws ClassNotFoundException

上面是loadClass方法的原型聲明,上面所說的雙親委託機制的實現其實就實在此方法中實現的。下面咱們就來看看此方法的代碼來看看它到底如何實現雙親委託的。

loadClass method implement

 
public Class<?> loadClass(String name) throws ClassNotFoundException
 {  
return loadClass(name, false);
}

從上面能夠看出loadClass方法調用了loadcClass(name,false)方法,那麼接下來咱們再來看看另一個loadClass方法的實現。

Class loadClass(String name, boolean resolve)

 

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
protected  synchronized  Class<?> loadClass(String name,  boolean  resolve)   throws  ClassNotFoundException   
  {   // First, check if the class has already been loaded  Class c = findLoadedClass(name);
//檢查class是否已經被加載過了  if (c == null)
  {     
  try  {      
if  (parent !=  null ) {         
  c = parent.loadClass(name,  false );  //若是沒有被加載,且指定了父類加載器,則委託父加載器加載。    
   else  {        
   c = findBootstrapClass0(name); //若是沒有父類加載器,則委託bootstrap加載器加載      } 
      catch  (ClassNotFoundException e) {         
  // If still not found, then invoke findClass in order          
// to find the class.         
  c = findClass(name); //若是父類加載沒有加載到,則經過本身的findClass來加載。      } 
 
  if  (resolve) 
{     
  resolveClass(c); 
  }  
return  c;
}

上面的代碼,我加了註釋經過註釋能夠清晰看出loadClass的雙親委託機制是如何工做的。 這裏咱們須要注意一點就是public Class<?> loadClass(String name) throws ClassNotFoundException沒有被標記爲final,也就意味着咱們是能夠override這個方法的,也就是說雙親委託機制是能夠打破的。另外上面注意到有個findClass方法,接下來咱們就來講說這個方法究竟是搞末子的。

2.findClass

咱們查看java.lang.ClassLoader的源代碼,咱們發現findClass的實現以下:

 

 protected Class<?> findClass(String name) throws ClassNotFoundException
 {  
throw new ClassNotFoundException(name);
}

咱們能夠看出此方法默認的實現是直接拋出異常,其實這個方法就是留給咱們應用程序來override的。那麼具體的實現就看你的實現邏輯了,你能夠從磁盤讀取,也能夠從網絡上獲取class文件的字節流,獲取class二進制了之後就能夠交給defineClass來實現進一步的加載。defineClass咱們再下面再來描述。 ok,經過上面的分析,咱們能夠得出以下結論:

咱們在寫本身的ClassLoader的時候,若是想遵循雙親委託機制,則只須要override findClass.

3.defineClass

咱們首先仍是來看看defineClass的源碼:

defineClass

 

protected final Class<?> defineClass(String name, byte[] b, int off, int len)  
throws ClassFormatError
{     
 return defineClass(name, b, off, len, null);
}

從上面的代碼咱們看出此方法被定義爲了final,這也就意味着此方法不能被Override,其實這也是jvm留給咱們的惟一的入口,經過這個惟 一的入口,jvm保證了類文件必須符合Java虛擬機規範規定的類的定義。此方法最後會調用native的方法來實現真正的類的加載工做。

Ok,經過上面的描述,咱們來思考下面一個問題:
假如咱們本身寫了一個java.lang.String的類,咱們是否能夠替換調JDK自己的類?

答案是否認的。咱們不能實現。爲何呢?我看不少網上解釋是說雙親委託機制解決這個問題,其實不是很是的準確。由於雙親委託機制是能夠打破的,你徹底能夠本身寫一個classLoader來加載本身寫的java.lang.String類,可是你會發現也不會加載成功,具體就是由於針對java.*開頭的類,jvm的實現中已經保證了必須由bootstrp來加載。

不遵循「雙親委託機制」的場景

上面說了雙親委託機制主要是爲了實現不一樣的ClassLoader之間加載的類的交互問題,被你們公用的類就交由父加載器去加載,可是Java中確實也存在父類加載器加載的類須要用到子加載器加載的類的狀況。下面咱們就來講說這種狀況的發生。

Java中有一個SPI(Service Provider Interface)標準,使用了SPI的庫,好比JDBC,JNDI等,咱們都知道JDBC須要第三方提供的驅動才能夠,而驅動的jar包是放在咱們應 用程序自己的classpath的,而jdbc 自己的api是jdk提供的一部分,它已經被bootstrp加載了,那第三方廠商提供的實現類怎麼加載呢?這裏面JAVA引入了線程上下文類加載的概 念,線程類加載器默認會從父線程繼承,若是沒有指定的話,默認就是系統類加載器(AppClassLoader),這樣的話當加載第三方驅動的時候,就可 以經過線程的上下文類加載器來加載。另外爲了實現更靈活的類加載器OSGI以及一些Java app server也打破了雙親委託機制。

相關文章
相關標籤/搜索