1. 程式人生 > 程式設計 >Java類載入器層次結構原理解析

Java類載入器層次結構原理解析

類載入器的層次結構:

引導類載入器(bootstrap class loader)

  用來載入java的核心庫(JAVA_HOME/jre/lib/rt.jar,或sun.boot.class.path路徑下的內容),是用原生程式碼來實現的(C實現的),並不繼承自java.lang.ClassLoader。

  載入擴充套件類和應用程式類載入器,並指定它們的父類載入器。

擴充套件類載入器(extensions class loader)

  用來載入java的擴充套件庫(JAVA_HOME/jre/lib/ext/*.jar,或java.ext.dirs路徑下的內容)java虛擬機器的實現會提供一個擴充套件庫目錄。該類載入器在此目錄裡面查詢並載入java類。

  有sun.miscLauncher$ExtClassLoader實現,繼承自java.lang.ClassLoader

應用程式類載入器(application class loader)

  它根據java應用的類路徑(classpath,java.class.path路徑)來載入指定路徑的類,一般來說,java應用的類都是由它來完成載入的

  由sun.misc.Launcher$AppClassLoader實現,繼承自java.lang.ClassLoader

自定義類載入器

  開發人員可以通過繼承java.lang.ClassLoader類的方式實現自己的類載入器,以滿足一些特殊的需求。

說明:在java中由於類的載入採用的是雙親委託機制,上面幾種類載入器是父子關係,其中引導類載入器為基礎。

ClassLoader類介紹

作用:

  java.lang.ClassLoader類的基本職責就是根據一個指定的類的名稱找到或者生成其對應的位元組程式碼,然後從這些位元組程式碼中定義出一個java類,即java.lang.Class類的一個例項。

  除此之外,ClassLoader還負責載入java應用所需的資原始檔,如影象檔案和配置檔案等。

相關方法:

  • getParent()  返回該類載入器的父類載入器
  • loadClass(String name)  載入名稱為name的類,返回的結果是java.lang.Class類的例項
  • findClass(String name)  查詢名稱為name的類,返回的結果是java.lang.Class類的例項
  • findLoadedClass(String name)  查詢名稱為name的已經被載入過的類,返回的結果是java.lang.Class類的例項
  • defineClass(String name,byte[] b,int off,int len)  把位元組陣列b中的內容轉換成java類,返回的結果是java.lang.Class類的例項。這個方法被宣告為final的。
  • resolveClass(Class<?> c)  連結指定的java類。

程式碼測試類載入器:

public class Demo02 {
  public static void main(String[] args) {
    System.out.println(ClassLoader.getSystemClassLoader());
    System.out.println(ClassLoader.getSystemClassLoader().getParent());;
    System.out.println(ClassLoader.getSystemClassLoader().getParent().getParent());;
  }
}

輸出:

sun.misc.Launcher$AppClassLoader@1016632

sun.misc.Launcher$ExtClassLoader@dc6a77

null

依次為應用載入器、擴充套件載入器和引導載入器(但是引導載入為原生程式碼所寫,因此獲取不到,為null)。

類載入器的代理模式:

代理模式:交給其他載入器來載入指定的類。

雙親委託機制:

  就是某個特定的類載入器在接到載入類的請求時,首先將載入任務委託給父類載入器,以此追溯,直到最高的爺爺輩的,如果父類載入器可以完成類載入任務,就成功返回;只有父類載入器無法完成此載入任務時,才自己去載入。

  雙親委託機制是為了保證java核心庫的型別安全(這種機制就保證不會出現使用者自己能定義java.lang.Object類的情況)。

  類載入器除了用於載入類,也是安全的最基本的屏障。

雙親委託機制是代理模式的一種:

  並不是所有的類載入器都採用雙親委託機制。

  tomcat伺服器類載入器也使用代理模式,所不同的是它是首先嚐試自己去載入某個類,如果找不到再代理給父類載入器。這與一般類載入器的順序是相反的。

自定義類載入器的流程:

  繼承:java.lang.ClassLoader

  首先檢查請求的型別是否已經被這個類裝載器裝載到名稱空間中,如果已經裝載,則返回

  委派類將載入請求給父類載入器,如果父類載入器能夠完成,則返回父類載入器載入的Class例項

  呼叫本類載入器的findClass()方法,師徒獲取對應的位元組碼,如果獲取得到,則呼叫defineClass()匯入型別到方法區;如果獲取不到對應的位元組碼或者其它原因失敗,則返回異常給loadClass(),loadClass()轉拋異常,終止載入過程

  注:被兩個載入器載入的同一個類,Jvm不認為是相同的類。

示例程式碼如下:

package com.test;

import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;

/**
 * 自定義檔案系統載入器
 * @author We.lxk
 *
 */
public class FileSystemClassLoader extends ClassLoader{
  private String rootDir;
  
  public FileSystemClassLoader(String rootDir) {
    this.rootDir = rootDir;
  }
  
  private byte[] getClassData(String classname){    //com.test.User -> rootDir/com/test/User
    String path = rootDir +"/"+classname.replace(".","/")+".class";
    //IOUtils 可以使用它將讀取的流資料轉換為位元組陣列
    InputStream is = null;
    ByteArrayOutputStream baos = new ByteArrayOutputStream(); 
    try{
      is = new FileInputStream(path);
      
      byte[] buffer = new byte[1024];
      int temp = 0;
      while((temp=is.read(buffer))!=-1){
        baos.write(buffer,temp);
      }
      return baos.toByteArray();
    }catch(Exception e){
      e.printStackTrace();
      return null;
    }finally{
        try {
          if(is!=null)
          is.close();
        } catch (IOException e) {
          // TODO Auto-generated catch block
          e.printStackTrace();
        }
        try {
          if(baos!=null)
          baos.close();
        } catch (IOException e) {
          // TODO Auto-generated catch block
          e.printStackTrace();
        }
    }
  }
  
  @Override
  public Class<?> loadClass(String name) throws ClassNotFoundException {
    Class<?> c = findLoadedClass(name);
    
    //應該先查詢有沒有載入過這個類。已經載入,則直接返回載入好的類。
    if(c!=null){
      return c;
    }else{
      ClassLoader parent = this.getParent();
      try{
        //System.out.println("hello");
        c = parent.loadClass(name);          //委派給父類載入
      }catch(Exception e){
        //e.printStackTrace();
      }
      if(c!=null){
        return c;
      }else{
        byte[] classData = getClassData(name);
        if(classData==null){
          throw new ClassNotFoundException();
        }else{
          c = defineClass(name,classData,classData.length);
        }
      }
    }
    return c;
  }
}

測試程式碼:

package com.test;

/**
 *  測試自定義的FileSystemClassLoader 
 * @author We.lxk
 *
 */
public class Demo03 {
  public static void main(String[] args) throws Exception {
    FileSystemClassLoader loader = new FileSystemClassLoader("D:/myJava");
    FileSystemClassLoader loader2 = new FileSystemClassLoader("D:/myJava");
    
    Class<?> c = loader.loadClass("com.test.Demos");
    Class<?> c2 = loader.loadClass("com.test.Demos");
    Class<?> c3 = loader2.loadClass("com.test.Demos");
    
    Class<?> c4 = loader2.loadClass("java.lang.String");
    Class<?> c5 = loader.loadClass("com.test.Demo");
    
    
    System.out.println(c.hashCode()+" "+c.getClassLoader());
    System.out.println(c2.hashCode()+" "+c2.getClassLoader());
    System.out.println(c3.hashCode()+" "+c3.getClassLoader());
    System.out.println(c4.hashCode()+" "+c4.getClassLoader());
    System.out.println(c5.hashCode()+" "+c5.getClassLoader());
    //System.out.println(.getClassLoader());
  }
}

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支援我們。