1. 程式人生 > >Java類的載入機制 ClassLoader

Java類的載入機制 ClassLoader

1.圖解JVM中的ClassLoader

   java應用環境中不同的class分別由不同的ClassLoader負責載入。
  一個jvm中預設的classloader有Bootstrap ClassLoader、Extension ClassLoader、App ClassLoader

  •  Bootstrap ClassLoader     負責載入java基礎類,主要是 %JRE_HOME/lib/ 目錄下的rt.jar、resources.jar、charsets.jar和class等
  •  Extension ClassLoader      負責載入java擴充套件類,主要是 %JRE_HOME/lib/ext 目錄下的jar和class
  •  App ClassLoader           負責載入當前java應用的classpath中的所有類。

其中Bootstrap ClassLoader是JVM級別的,由C++撰寫;Extension ClassLoader、App ClassLoader都是java類,都繼承自URLClassLoader超類。
Bootstrap ClassLoader由JVM啟動,然後初始化sun.misc.Launcher ,sun.misc.Launcher初始化Extension ClassLoader、App ClassLoader。

下圖是ClassLoader的載入類流程圖,以載入一個類的過程類示例說明整個ClassLoader的過程。



 Bootstrap ClassLoader、Extension ClassLoader、App ClassLoader三者的關係如下:

Bootstrap ClassLoader是Extension ClassLoader的parent,Extension ClassLoader是App ClassLoader的parent。

但是這並不是繼承關係,只是語義上的定義,基本上,每一個ClassLoader實現,都有一個Parent ClassLoader。

可以通過ClassLoader的getParent方法得到當前ClassLoader的parent。Bootstrap ClassLoader比較特殊,因為它不是java class所以Extension ClassLoader的getParent方法返回的是NULL。

如何自定義ClassLoader?

由於一些特殊的需求,我們可能需要定製ClassLoader的載入行為,這時候就需要自定義ClassLoader了.

自定義ClassLoader需要繼承ClassLoader抽象類,重寫findClass方法,這個方法定義了ClassLoader查詢class的方式。

主要可以擴充套件的方法有:

findClass          定義查詢Class的方式

defineClass       將類檔案位元組碼載入為jvm中的class

findResource    定義查詢資源的方式

如果嫌麻煩的話,我們可以直接使用或繼承已有的ClassLoader實現,比如

  • java.net.URLClassLoader
  • java.security.SecureClassLoader
  • java.rmi.server.RMIClassLoader
  • sun.applet.AppletClassLoader

Extension ClassLoader 和 App ClassLoader都是java.net.URLClassLoader的子類。

這個是URLClassLoader的構造方法:

public URLClassLoader(URL[] urls, ClassLoader parent)

public URLClassLoader(URL[] urls)

urls引數是需要載入的ClassPath url陣列,可以指定parent ClassLoader,不指定的話預設以當前呼叫類的ClassLoader為parent。

ClassLoader classLoader = new URLClassLoader(urls);
Thread.currentThread().setContextClassLoader(classLoader);
Class clazz=classLoader.loadClass("com.company.MyClass");//使用loadClass方法載入class,這個class是在urls引數指定的classpath下邊。

Method taskMethod = clazz.getMethod("doTask", String.class, String.class);//然後我們就可以用反射做些事情了
taskMethod.invoke(clazz.newInstance(),"hello","world");

由於classloader 載入類用的是全盤負責委託機制。所謂全盤負責,即是當一個classloader載入一個Class的時候,這個Class所依賴的和引用的所有 Class也由這個classloader負責載入,除非是顯式的使用另外一個classloader載入。

所以,當我們自定義的classloader載入成功了com.company.MyClass以後,MyClass裡所有依賴的class都由這個classLoader來載入完成。

自定義ClassLoader在某些應用場景還是比較適用,特別是需要靈活地動態載入class的時候。


2.Java中的類裝載機制

也許你認為Class Load是一個高階話題,不管怎樣,作為開發者你還是要了解它。 本文基於最新得JDK5,然而將訴的內容卻包含了最基本的原理,希望你能更加深入瞭解自己所使用得語言。
理解CLassLoader 如果你自己定義了一個 org.test.Object 。 你在程式中這樣寫: import ort.test.Object Object o = new String(); 
也許你欣然以為這樣寫沒問題,但實際上你錯了。 
一個Class在JVM中得標識是由它得 PackAge 和 類名決定得(也就是它的名稱空間)所決定的。  org.test.Object 並不等同於  java.lang.Object 在java中,每個類都是java.lang.Class得例項。所有的類都可以這樣自定義:
java.lang.Class klass = Myclass.class;
而例項化一個類,可以是:Myclass myclass = new Myclass() 也可以是: myclass.newInstance();
在JVM中。所有得類都由  java.lang.ClassLoader .以及它的子類載入的,我們在執行程式的時候,首先要從 JAVA_HOME/jre/rt.jar開始。  不過我們發現JDK文件裡並沒有介紹bootstrap.jar。 實際上bootstrap 是JDK之外得,它得方式和JVM的是不一樣的。

JDK中除了ClassLoader可以載入類之外,還有以下這些也可以。
  • java.net.URLClassLoader
  • java.security.SecureClassLoader
  • java.rmi.server.RMIClassLoader
  • sun.applet.AppletClassLoader
Class Loaders工作原理   除了bootstrap之外,所有的Classloader都有個父類Class Loader ,他們都是instanceof java.lang.ClassLoader 看看JDK1.5中的一個例子 假如有個方法loadClass protected synchronized Class<?> loadClass
    (String name, boolean resolve)
    throws ClassNotFoundException{

    // First check if the class is already loaded
    Class c = findLoadedClass(name);
    if (c == null) {
        try {
            if (parent != null) {
                c = parent.loadClass(name, false);
            } else {
                c = findBootstrapClass0(name);
            }
        } catch (ClassNotFoundException e) {
            // If still not found, then invoke
            // findClass to find the class.
            c = findClass(name);
        }
    }
    if (resolve) {
    resolveClass(c);
    }
    return c;
}
那設定它父類的方式有兩種。 public class MyClassLoader extends ClassLoader{
    public MyClassLoader(){
        super(MyClassLoader.class.getClassLoader());
    }
}
或者 public class MyClassLoader extends ClassLoader{
    public MyClassLoader(){
        super(getClass().getClassLoader());
    }
}
這裡是首選第一種。 因為getClass()是在建構函式內部得方法,所以必須要有建構函式程式碼存在,但是如果不存在,那就找父類得classloader 一直往上找,直至找到到findBootstrapClass, 如果它也不存在得話,那時候findClass()方法會被呼叫執行。。 (那時候會報一個ClassNotFoundException)  來看看findClass()得程式碼
    protected Class<?> findClass(String name)
        throws ClassNotFoundException {
        throw new ClassNotFoundException(name);
    }
在findClass() 方法內 class loader要取得到得位元組碼(就是編譯後*.class檔案裡得內容),也不一定就是.class檔案, 這些位元組碼可以來自本地,也可以是系統,網路(藉著這個你可以理解一下Cobra,RMI),也可以是用 BCEL (Apache一個基於位元組碼得一個引擎庫)  ...等等。 一但位元組碼找到了。那時候就開始執行 defineClass()方法。那時候ClassLoader便會定義出一個類來。
 
每個ClassLoader出來的類都是不同的, 如果有兩個ClassLoader載入兩各相同的程式,defineClass()定義得兩個類也是不同得。詳細請看(
 Java language specification  下面有幅圖畫 展示了一個MyMainclass.class是如何裝載執行的。(由多個classLoader載入同一個Target.class),----
根據上面得解析,既然由兩個classLoader()載入Target.class得位元組碼 ,那defineClass()就會產生兩個class的定義。 所以很容易得出以下結論:
Target target1 = (Target) target2; 是不正確的 。target1 和 target2是由兩個不同的classloader定義的。

具體請看 Inside Class Loaders (Andreas Schaefer)




 我們是否需要自定義的ClassLoader?     理由之一: 如果我們自定義了ClassLoader,那我們便可以控制JVM的載入動作了。
   
   上面說一個class標識是由於package+classname組成得。 對於所有實現java.io.
 Serializable介面的類,都是由 serialVersionUID 管理這些類得版本(RMI,JNDI,Security裡都有這樣一個ID) 。它用64位的Hash來表示 (這個Hash由classname,filed,method組成)。從技術上講如果classname,field,mehtod所構成的Hash都一樣,那就會認為是同一個版本。 
  假設有這樣一個情況,我們要寫一個java 執行引擎(比如:用一個RMI 釋出一個Server端程式,執行client的介面方法) ,  既然要能執行,那引擎肯定要實現有Client所特定任務的介面(這裡為TaskIntf)。  一但任務提交給執行引擎,Server要做的第一件事情就是要裝載所有要執行的程式碼。 假設不同的終端遞交了不同的程式碼。而偏偏又都是同樣的包名,和同樣的類名。 那伺服器能否會辨別到底是那個Client提交過來的執行請求?   現在出個問題: 如果在伺服器端一個執行程式執行兩個客戶端提交同一個版本得程式碼,如何才讓客戶端會得到預期的執行結果?    別以為這個很簡單,下面先建個RMI玩玩。看看結果會是怎樣。   本地檔案如下圖。
圖 2 程式目錄結構 (本文中含程式碼)。 在samepath目錄下, 有著兩個version.Version.class,他們得包名類名都一樣,唯一不同的是。 v1目錄中的方法是:     public void fx(){
        log("this = " + this + "; Version.fx(1).");
    }
v2目錄中的方法是:     public void fx(){
        log("this = " + this + "; Version.fx(2).");
    }
執行一下看看: set CLASSPATH=.;%CURRENT_ROOT%/v1;%CURRENT_ROOT%/v2
%JAVA_HOME%/bin/java Test 結果如下圖


圖 3. classPath得目錄設為v1

切換到 set CLASSPATH=.;%CURRENT_ROOT%/v2;%CURRENT_ROOT%/v1
%JAVA_HOME%/bin/java Test
結果如下圖:


圖 4. classpath目錄設為v2

很明顯,上面的例子中能從classpath中找到先後次序。如果我們把v1,v2的version.Version。都刪調。而把他們打成一個myextension.jar包,放到java.ext.dirs目錄下。。這時候就通過ExtClassLoader來裝載了,而不是AppClassLoader.

結果會是如下:


圖 5. AppClassLoader and ExtClassLoader

繼續往下看,另外一個例子。 在differentversions 目錄下的例子,裡面包含了RMI的ServerImpl這樣一個執行引擎。Client實現了common.TaskIntf介面。 兩個 client.TaskImpl分別如下:

    static{
        log("client.TaskImpl.class.getClassLoader
        (v1) : " + TaskImpl.class.getClassLoader());
    }

    public void execute(){
        log("this = " + this + "; execute(1)");
    }

 另一個則是:

 static{
        log("client.TaskImpl.class.getClassLoader
        (v1) : " + TaskImpl.class.getClassLoader());
    }

    public void execute(){
        log("this = " + this + "; execute(2)");
    }

這樣子來執行(順序隨便,這裡把 %CURRENT_ROOT%/client2放在前面):

CLASSPATH=%CURRENT_ROOT%/common;%CURRENT_ROOT%/server;
    %CURRENT_ROOT%/client2;%CURRENT_ROOT%/client1
%JAVA_HOME%/bin/java server.Server

先啟動Server..

  分別把兩個client提交給伺服器執行, (即便執行程式中得client1.bat 和 client2.bat server監控螢幕如圖6所示。) 


圖 6. Execution Engine Server console

再來看下面兩個圖(圖7和圖8),分別是client端得執行顯示。


圖 7. Execution Engine Client 1 console


圖 8. Execution Engine Client 2 console

縱觀上面三次執行結果,發現由於伺服器啟動得時候使用了AppClassLoader.所以無論怎麼樣都是載入得是client2(因為client2的classpath次序比較在前),

這裡client1 很鬱悶,它在自己那執行明明是  execute(1) 通過 RMI 傳送給伺服器端執行就成了 execute(2)..

值得注意的是: 在client1,client2分別傳送給伺服器執行之後,伺服器端顯示的記錄是:
client.TaskImpl.class.getClassLoader(v2):[email protected]@xxxx zhiz只執行了一次。而
[email protected] execute(2);執行了兩次

上面已經講到過了,對於一個ClassLoader來講 同樣的page+className 只能定義一個 class,而不同的ClassLoader即便載入同一個page.className 也會定義不同的class

到這裡,我才發現,解決上面提出得那個問題似乎並不容易。:(。

那如何解決呢?答案就是---使用自定義得classLoader ..

如果各位等不急的話, 先請看(目錄中 differentversionspush 裡面的程式碼)

很顯然,我們很有必要寫自定義的classloader.


如何構造使用自定義的ClassLoader

既然自定義的ClassLoader,能解決上述問題,那接下去看看,我們如何來使用自定義的ClassLoader。

結合本文種的原碼---(在differentversionspush的目錄裡),有個FileSystemClassLoader,類圖描述如下:


圖9.

看看他的方法 findClassBytes(String className);

    public byte[] findClassBytes(String className){

        try{
            String pathName = currentRoot +
                File.separatorChar + className.
                replace('.', File.separatorChar)
                + ".class";
            FileInputStream inFile = new
                FileInputStream(pathName);
            byte[] classBytes = new
                byte[inFile.available()];
            inFile.read(classBytes);
            return classBytes;
        }
        catch (java.io.IOException ioEx){
            return null;
        }
    }

    public Class findClass(String name)throws
        ClassNotFoundException{

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

    public Class findClass(String name, byte[]
        classBytes)throws ClassNotFoundException{

        if (classBytes==null){
            throw new ClassNotFoundException(
                "(classBytes==null)");
        }
        else{
            return defineClass(name, classBytes,
                0, classBytes.length);
        }
    }

    public void execute(String codeName,
        byte[] code){

        Class klass = null;
        try{
            klass = findClass(codeName, code);
            TaskIntf task = (TaskIntf)
                klass.newInstance();
            task.execute();
        }
        catch(Exception exception){
            exception.printStackTrace();
        }
    }

這個類FileSystemClassLoader 被client使用了,用來定義class, 並且把它把client.TaskImpl(v1)轉化為 byte[], 然後 byte[]傳送到RMI Server執行。(上面講了defineClass()能夠執行任何位元組碼,來自編譯後的檔案,網路甚至是BCEL 位元組碼引擎庫),   在Server端 ,又可以通過FileSystemClassLoader 以為byte[]的形式定義出 client.TaskImpl。

請看Client端的程式碼:

public class Client{

    public static void main (String[] args){

        try{
            byte[] code = getClassDefinition
                ("client.TaskImpl");
            serverIntf.execute("client.TaskImpl",
                code);
            }
            catch(RemoteException remoteException){
                remoteException.printStackTrace();
            }
        }

    private static byte[] getClassDefinition
        (String codeName){
        String userDir = System.getProperties().
            getProperty("BytePath");
        FileSystemClassLoader fscl1 = null;

        try{
            fscl1 = new FileSystemClassLoader
                (userDir);
        }
        catch(FileNotFoundException
            fileNotFoundException){
            fileNotFoundException.printStackTrace();
        }
        return fscl1.findClassBytes(codeName);
    }
}

在RMI伺服器端ServerImpl 程式裡, 接受到來自client的位元組碼(byte[]),於是FileSystemClassLoader 會從byte[]構造出一個class, 例項話,並且執行。 

有一點要注意:每次接收到一個client的請求,FileSystemClassLoader都會重新例項化(執行結果中可以看出來),這就意味著,client.Impl不在是在classpath中被找到的,而是通過FileSystemClassLoader 的findClass() 來執行deFineClass(),這樣每次 FileSystemClassLoader 都是建立新的例項,,自然 deFine出來的class也是不同的。 這樣,我們就能在RMI的執行中區分出 這兩個class來。(client.TaskImpl != client.TaskImp  在上篇就已經得出結論了。 )

看看伺服器端的執行程式碼:

public void execute(String codeName, byte[] code)throws RemoteException{

        FileSystemClassLoader fileSystemClassLoader = null;

        try{
            fileSystemClassLoader = new FileSystemClassLoader();
            fileSystemClassLoader.execute(codeName, code);
        }
        catch(Exception exception){
            throw new RemoteException(exception.getMessage());
        }
    }

伺服器端的執行結果:


圖10,伺服器端顯示

下面兩圖分別是客戶端顯示的。


圖11. client1的執行顯示



圖12. client2執行結果

哈,上面洋洋灑灑那麼多,總算是一步一步的教會了大家 如何在同一個VM虛擬機器中,執行“不同版本”的程式碼 。(這些程式碼有同樣的類名和包名)。

Class Loaders 在 J2EE 中應用。

到這裡你其實已經不足為奇下面一些東西了。。。
      我的一個A_war.war的web專案中 程式碼是 com.mycom.Test 而我在另外一個B_war.war的wenb專案中的 程式碼也是com.mycom.Test 而他們照樣工作的好好的。 
      當一個大型的 EJB專案,一臺伺服器上部署了多個 EJB,War工程時候,他們也不會互相影響。AppServer還會有自己的裝載策略,比如你web中用的jar包,會優先於AppServer本身所帶有的。

相關推薦

Java 載入機制 ClassLoader Class.forName 記憶體管理 垃圾回收GC

類載入是Java程式執行的第一步,研究類的載入有助於瞭解JVM執行過程,並指導開發者採取更有效的措施配合程式執行。 研究類載入機制的第二個目的是讓程式能動態的控制類載入,比如熱部署等,提高程式的靈活性和適應性。 一、簡單過程 Java

【正文】Java載入器( CLassLoader ) 死磕 4: 神祕的雙親委託機制

【正文】Java類載入器(  CLassLoader ) 死磕4:  神祕的雙親委託機制 本小節目錄 4.1. 每個類載入器都有一個parent父載入器 4.2. 類載入器之間的層次關係 4.3. 類的載入次序 4.4 雙親委託機制原理與沙箱機制 4.5. forName方法和load

jvm之java載入機制載入器(ClassLoader)的詳解

     當程式主動使用某個類時,如果該類還未被載入到記憶體中,則JVM會通過載入、連線、初始化3個步驟來對該類進行初始化。如果沒有意外,JVM將會連續完成3個步驟,所以有時也把這個3個步驟統稱為類載入或類初始化。                             

java載入器——ClassLoader

web rac rgb 好的 全盤負責機制 安全 trac 字節 如何 Java的設計初衷是主要面向嵌入式領域,對於自己定義的一些類,考慮使用依需求載入原則。即在程序使用到時才載入類,節省內存消耗,這時就可以通過類載入器來動態載入。 假設你平時僅僅是做web開發,那應該

Java載入ClassLoader的解析

index html dir obj ble body 6.4 odin 普通 //參考 : http://www.ibm.com/developerworks/cn/java/j-lo-classloader/ 類載入器基本概念 類載

從阿里巴巴面試題到java載入機制

首先很經典的阿里巴巴面試題 加上我自己的一些疑惑程式碼 public class Text { public static int k = 0; public final int k1 = 3; //自己加的 public static Text t1 = new Text("

Java載入器( CLassLoader ) 死磕9: 上下文載入器原理和案例

【正文】Java類載入器(  CLassLoader ) 死磕9:  上下文載入器原理和案例 本小節目錄 9.1. 父載入器不能訪問子載入器的類 9.2. 一個寵物工廠介面 9.3. 一個寵物工廠管理類 9.4 APPClassLoader不能訪問子載入器中的類 9.5. 執行緒上下文

Java載入器( CLassLoader ) 死磕 6: 自定義網路載入

【正文】Java類載入器(  CLassLoader ) 死磕 6:  自定義網路類載入器 本小節目錄 6.1. 自定義網路類載入器的類設計 6.2. 檔案傳輸Server端的原始碼 6.3. 檔案傳輸Client端的原始碼 6. 4 自定義載入器SocketClassLoader的原始

Java載入器( CLassLoader ) 死磕8: 使用ASM,和載入器實現AOP

【正文】Java類載入器(  CLassLoader ) 死磕8:  使用ASM,和類載入器實現AOP 本小節目錄 8.1. ASM位元組碼操作框架簡介 8.2. ASM和訪問者模式 8.3. 用於增強位元組碼的事務類 8.4 通過ASM訪問註解 8.5. 通過ASM注入AOP事務程式

Java載入器( CLassLoader ) 死磕7: 基於加密的自定義網路載入器 本小節目錄

【正文】Java類載入器(  CLassLoader ) 死磕7:  基於加密的自定義網路載入器 本小節目錄 7.1. 加密傳輸Server端的原始碼 7.2. 加密傳輸Client端的原始碼 7.3. 使用亦或實現簡單加密和解密演算法 7. 網路加密SafeClassLoader的原

Java載入器( CLassLoader ) 死磕5: 自定義一個檔案系統的classLoader

【正文】Java類載入器(  CLassLoader ) 死磕5:  自定義一個檔案系統classLoader 本小節目錄 5.1. 自定義類載入器的基本流程 5.2. 入門案例:自定義檔案系統類載入器 5.3. 案例的環境配置 5.4 FileClassLoader 案例實現步驟 5

Java載入器( CLassLoader ) 死磕 3: 揭祕 ClassLoader抽象基

  【正文】Java類載入器(  CLassLoader ) 死磕 3: 揭祕 ClassLoader抽象基類 (1)一個載入器的parent是誰? (2)為什麼優先從parent載入,而不是從自己的地盤載入?欲知後事如何,請看下回分解。 原始碼: 程式碼工程:  class

Java載入器( CLassLoader ) 死磕 1、2: 匯入 & 分類

JAVA類載入器 死磕系列 目錄 by   瘋狂創客圈 1.匯入1.1. 從class檔案的載入開始1.2. 什麼是類載入器2. JAVA類載入器分類2.1. 作業系統的環境變數2.2. Bootstrap ClassLoader(啟動類載入器)2.3. Extention ClassL

java載入機制和自定義載入

類載入順序 上圖所示的是類載入的順序,按照大的順序可以分為載入、連結、初始化 其中連結又可以分成驗證、準備、解析三個步驟 載入 1.將類的class檔案讀入到記憶體中 載入類檔案的方式有: 1. 本機檔案載入 2.jar包載入 3.網路載入 4.原始檔動態編譯載入

淺談Java載入機制

最近在學習 Tomcat 架構,其中很重要的一個模組是類載入器,因為以前學習的不夠深入,所以趁這個機會好好把類載入機制搞明白。 概述 類載入器主要分為兩類,一類是 JDK 預設提供的,一類是使用者自定義的。 JDK 預設提供三種類載入器 Bootstrap ClassLo

深入理解Java載入機制(一)

1 前言: 在上一篇文章一文讓你明白 Java 位元組碼中, 我們瞭解了java位元組碼的解析過程,那麼在接下來的內容中,我們來了解一下類的載入機制。 2 題外話 Java的核心是什麼?當然是JVM了,所以說了解並熟悉JVM對於我們理解Java語言非常重要,不管你是做Java還是Andr

Java 載入機制詳解

一、類載入器   類載入器(ClassLoader),顧名思義,即載入類的東西。在我們使用一個類之前,JVM需要先將該類的位元組碼檔案(.class檔案)從磁碟、網路或其他來源載入到記憶體中,並對位元組碼進行解析生成對應的Class物件,這就是類載入器的功能。我們可以利用類載入器,實現類的動態載入。 二、類的

Java載入機制

類載入機制 概念 類載入器把class檔案中的二進位制資料讀入到記憶體中,存放在方法區,然後在堆區建立一個java.lang.Class物件,用來封裝類在方法區內的資料結構。 1、載入: 查詢並載入類的二進位制資料(把class檔案裡面的資訊載入到記憶體裡面) 2

JVM——Java載入機制

class Singleton{ private static Singleton singleton = new Singleton(); public static int value1; public static int valu

JAVA 載入機制學習筆記

JAVA 類生命週期     如上圖所示,Java類的生命週期如圖所示,分別為載入、驗證、準備、解析、初始化、使用、解除安裝。其中驗證、準備、解析這三個步驟統稱為連結。   載入:JVM根據全限定名來獲取一段二進位制位元組流,將二進位制流轉化為方法區的執行時資料結構,在記憶體中生成一個代表