java ClassLoader的學習

通過這篇文章,可以大體的瞭解類的加載機制與運用。

比較難理解的是什麼是線程上下文類加載器,爲什麼需要。
其實線程上下文加載器爲類加載機制提供了後門。
雙親委派模型,是由下向上單向尋找類(system->extension->bootstrap)。
例如java.sql包中的類由bootstrap或extension classloader加載,而mysql驅動包是在classpath中由system來加載,但bootstrap中的類是無法找到system classloader中的類,此時靠線程上下文類加載器來解決。線程上下文類加載器主要就是能讓jvm類加載模型具有了向下尋找的可能,bootstrap->extension->system,如果不做任何設置,線程上下文類加載器默認是system classloader。

動態加載資源時,往往需要從三種類加載器裏選擇:系統或說程序的類加載器、當前類加載器、以及當前線程的上下文類加載器。在程序中應該使用何種類加載器呢?

系統類加載器通常不會使用。此類加載器處理啓動應用程序時classpath指定的類,可以通過ClassLoader.getSystemClassLoader()來獲得。所有的ClassLoader.getSystemXXX()接口也是通過這個類加載器加載的。一般不要顯式調用這些方法,應該讓其他類加載器代理到系統類加載器上。由於系統類加載器是JVM最後創建的類加載器,這樣代碼只會適應於簡單命令行啓動的程序。一旦代碼移植到EJB、Web應用或者Java Web Start應用程序中,程序肯定不能正確執行。

因此一般只有兩種選擇,當前類加載器和線程上下文類加載器。當前類加載器是指當前方法所在類的加載器。這個類加載器是運行時類解析使用的加載器,Class.forName(String)和Class.getResource(String)也使用該類加載器。代碼中X.class的寫法使用的類加載器也是這個類加載器。

線程上下文類加載器在Java 2(J2SE)時引入。每個線程都有一個關聯的上下文類加載器。如果你使用new Thread()方式生成新的線程,新線程將繼承其父線程的上下文類加載器。如果程序對線程上下文類加載器沒有任何改動的話,程序中所有的線程將都使用系統類加載器作爲上下文類加載器。Web應用和Java企業級應用中,應用服務器經常要使用複雜的類加載器結構來實現JNDI(Java命名和目錄接口)、線程池、組件熱部署等功能,因此理解這一點尤其重要。


爲什麼要引入線程的上下文類加載器?將它引入J2SE並不是純粹的噱頭,由於Sun沒有提供充分的文檔解釋說明這一點,這使許多開發者很糊塗。實際上,上下文類加載器爲同樣在J2SE中引入的類加載代理機制提供了後門。
通常JVM中的類加載器是按照層次結構組織的,目的是每個類加載器(除了啓動整個JVM的原初類加載器)都有一個父類加載器。當類加載請求到來時,類加載器通常首先將請求代理給父類加載器。只有當父類加載器失敗後,它才試圖按照自己的算法查找並定義當前類。

有時這種模式並不能總是奏效。這通常發生在JVM核心代碼必須動態加載由應用程序動態提供的資源時。拿JNDI爲例,它的核心是由JRE核心類(rt.jar)實現的。但這些核心JNDI類必須能加載由第三方廠商提供的JNDI實現。這種情況下調用父類加載器(原初類加載器)來加載只有其子類加載器可見的類,這種代理機制就會失效。解決辦法就是讓核心JNDI類使用線程上下文類加載器,從而有效的打通類加載器層次結構,逆着代理機制的方向使用類加載器。


好了,現在我們明白了問題的關鍵:這兩種選擇不可能適應所有情況。一些人認爲線程上下文類加載器應成爲新的標準。但這在不同JVM線程共享數據來溝通時,就會使類加載器的結構亂七八糟。除非所有線程都使用同一個上下文類加載器。而且,使用當前類加載器已成爲缺省規則,它們廣泛應用在類聲明、Class.forName等情景中。即使你想儘可能只使用上下文類加載器,總是有這樣那樣的代碼不是你所能控制的。這些代碼都使用代理到當前類加載器的模式。混雜使用代理模式是很危險的。

更爲糟糕的是,某些應用服務器將當前類加載器和上下文類加器分別設置成不同的ClassLoader實例。雖然它們擁有相同的類路徑,但是它們之間並不存在父子代理關係。想想這爲什麼可怕:記住加載並定義某個類的類加載器是虛擬機內部標識該類的組成部分,如果當前類加載器加載類X並接着執行它,如JNDI查找類型爲Y的數據,上下文類加載器能夠加載並定義Y,這個Y的定義和當前類加載器加載的相同名稱的類就不是同一個,使用隱式類型轉換就會造成異常。

下面是我的測試代碼:

/**
*
*/

package myPackage;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Method;

/**
* @author fanchi_fan
*
*/

public class FileSystemClassLoader extends ClassLoader {

  /**
    * @param args
    */


  private String rootDir;

  public FileSystemClassLoader(String rootDir) {
    this.rootDir = rootDir;
  }

  @Override
  protected Class<?> findClass(String name) throws ClassNotFoundException {
    // TODO Auto-generated method stub

    byte[] classData = getClassData(name);
    if (classData == null) {
      throw new ClassNotFoundException();
    } else {
      return defineClass(name, classData, 0, classData.length);
    }

    // return super.findClass(arg0);
  }

  private byte[] getClassData(String className) {
    String path = classNameToPath(className);
    try {
      InputStream ins = new FileInputStream(path);
      ByteArrayOutputStream baos = new ByteArrayOutputStream();
      int bufferSize = 4096;
      byte[] buffer = new byte[bufferSize];
      int bytesNumRead = 0;
      while ((bytesNumRead = ins.read(buffer)) != -1) {
        baos.write(buffer, 0, bytesNumRead);
      }
      return baos.toByteArray();
    } catch (IOException e) {
      e.printStackTrace();
    }
    return null;
  }

  private String classNameToPath(String className) {
    return rootDir + File.separatorChar
        + className.replace('.', File.separatorChar) + ".class";
  }

  public static void main(String[] args) {
    // TODO Auto-generated method stub
    String classDataRootPath = "C:\\WorkSpace\\webserviceServer\\bin";
    FileSystemClassLoader fscl1 = new FileSystemClassLoader(
        classDataRootPath);
    String className = "com.test.Animal";
    try {
      Class<?> class1 = fscl1.loadClass(className);
      // Animal obj1 = (Animal) class1.newInstance();
      // obj1.say();

      Object obj1 = class1.newInstance();
      Method say = class1.getMethod("say");
      say.invoke(obj1);
      
      Thread t=new Thread(){
        public void run(){
//          Thread t=new Thread(){
//            public void run(){
//              
//            }
//          };
//          System.out.println(t.getContextClassLoader());
          
        }
      };
      t.setContextClassLoader(fscl1);
      
      System.out.println(t.getContextClassLoader());
      t.start();
      
    } catch (Exception e) {
      e.printStackTrace();
    }


  }
}

/**
*
*/

package com.test;

/**
* @author fanchi_fan
*
*/

public class Animal {
  public void say() throws ClassNotFoundException{
    System.out.println("say hello!");
//    Thread t=new Thread(){};
    System.out.println(TestClient.class.getClassLoader());
    System.out.println(this.getClass().getClassLoader());
    System.out.println(Class.forName("com.test.TestClient").getClassLoader());
  }
}

測試過程中發現,在eclipse中你會發現所有的加載器都是系統類加載器。這是因爲在eclipse中,你在工程中啓動運行的類,eclipse會先加載工程中類路徑中的類和工程中的所有你寫類。所以,你如果通過cmd運行或者是 不同工程才能等到正確的輸出。

bootstrap classloader - 引導(也稱爲原始)類加載器,它負責加載Java的核心類。在Sun的JVM中,在執行java的命令中使用-Xbootclasspath選項或使用-D選項指定sun.boot.class.path系統屬性值可以指定附加的類。這個加載器的是非常特殊的,它實際上不是java.lang.ClassLoader的子類,而是由JVM自身實現的。大家可以通過執行以下代碼來獲得bootstrap classloader加載了那些核心類庫:
    URL[] urls=sun.misc.Launcher.getBootstrapClassPath().getURLs();
    for (int i = 0; i < urls.length; i++) {
      System.out.println(urls[i].toExternalForm());
    }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章