jvm(1)類加載(一)(加載過程,雙親加載)

 JVM類加載器機制與類加載過程html

jvm虛擬機的種類:java

Hotspot(Oracle)(基本上都是在說這個)
J9, JikesRVM(IBM)
Zulu, Zing (Azul)

Launcher是一直用於啓動JVM進程的啓動器,有兩種:程序員

一種windows平臺下運行時會保留在控制檯 
一種用於執行Java的GUI程序,不會顯示任何程序的輸出信息

Launcher只是一個封裝了虛擬機的執行外殼,由它負責裝載JRE環境和windows平臺下的jvm.dll動態連接庫算法

 

補充:bootstrap

HotSpot虛擬機對象的建立、內存佈局、訪問定位windows

PerformanceCounter 表示 Windows NT 性能計數器組件 api

一:跨平臺:

Java語言之因此說它是跨平臺的、由於Java語言的運行環境是在Java虛擬機中。數組

 Java虛擬機對各個平臺而言,實質上是各個平臺上的一個可執行程序。java虛擬機對於windows而言,就是一個java.exe進程而已。

二:Java虛擬機啓動、加載類過程分析

package org.luanlouis.jvm.load;  
import sun.security.pkcs11.P11Util;  
public class Main{  
    public static void main(String[] args) {  
        System.out.println("Hello,World!");  
        ClassLoader loader = P11Util.class.getClassLoader();    
        System.out.println(loader);  
    }  
}  

執行java    org.luanlouis.jvm.load.Main安全

windows開始運行{JRE_HOME}/bin/java.exe程序,java.exe 程序將完成如下步驟:網絡

1. 給jvm分配內存空間: 根據JVM內存配置要求,爲JVM申請特定大小的內存空間;
2. 類加載器建立和加載系統類到方法區:建立一個引導類加載器實例,初步加載系統類到內存方法區區域中;
3. 啓動器建立和獲取類加載器:建立JVM 啓動器實例 Launcher,並取得類加載器ClassLoader;
4. 類加載器加載自定義類:使用上述獲取的ClassLoader實例加載咱們定義的 org.luanlouis.jvm.load.Main類;
5. jvm執行main方法:加載完成時候JVM會執行Main類的main方法入口,執行Main類的main方法;
6. 結束,java程序運行結束,JVM銷燬。

1,根據JVM內存配置要求,爲JVM申請特定大小的內存空間

2,建立一個引導類加載器實例,初步加載系統類到內存方法區區域中;

JVM申請好內存空間後,JVM會建立一個引導類加載器(Bootstrap Classloader)實例,加載系統類:

引導類加載器是使用C++語言實現的,負責加載JVM虛擬機運行時所需的基本系統級別的類,如java.lang.String, java.lang.Object等等。

引導類加載器(Bootstrap Classloader)會讀取 {JRE_HOME}/lib 下的jar包和配置,而後將這些系統類加載到方法區內。

也可使用參數 -Xbootclasspath 或 系統變量sun.boot.class.path來指定的目錄來加載類。

通常而言,{JRE_HOME}/lib下存放着JVM正常工做所須要的系統類,以下表所示:

文件名                 描述
rt.jar            運行環境包,rt即runtime,J2SE 的類定義都在這個包內
charsets.jar      字符集支持包
jce.jar           是一組包,它們提供用於加密、密鑰生成和協商以及 Message Authentication Code(MAC)算法的框架和實現
jsse.jar          安全套接字拓展包Java(TM) Secure Socket Extension
classlist         該文件內表示是引導類加載器應該加載的類的清單
net.properties    JVM 網絡配置信息 

引導類加載器(Bootstrap ClassLoader) 加載系統類後,JVM內存會呈現以下格局:

1,引導類加載器將類信息加載到方法區中,以特定方式組織,對於某一個特定的類而言,
在方法區中它應該有 運行時常量池、類型信息、字段信息、方法信息、類加載器的引用,對應class實例的引用等信息。 2,類加載器的引用,因爲這些類是由引導類加載器(Bootstrap Classloader)進行加載的,而引導類加載器是有C
++語言實現的,因此是沒法訪問的,故而該引用爲NULL 3,對應class實例的引用, 類加載器在加載類信息放到方法區中後,會建立一個對應的Class 類型的實例放到堆(Heap)中, 做爲開發人員訪問方法區中類定義的入口和切入點。

補充:

1,也就是說程序員使用的類的數據都是(經過類加載器建立的每一個類的class)Class實例來獲取的。(方法區和堆的交互是加載類完成的)

2,當咱們在代碼中嘗試獲取系統類如java.lang.Object的類加載器時,你會始終獲得NULL:

System.out.println(String.class.getClassLoader());//null  
System.out.println(Object.class.getClassLoader());//null  
System.out.println(Math.class.getClassLoader());//null  
System.out.println(System.class.getClassLoader());//null  

3,建立JVM 啓動器實例 Launcher,並取得類加載器ClassLoader

上述步驟完成,JVM基本運行環境就準備就緒了。要讓JVM工做起來:運行咱們定義的程序 org.luanlouis,jvm.load.Main。

須要JVM虛擬機調用已經加載在方法區的類sun.misc.Launcher 的靜態方法getLauncher(),  獲取sun.misc.Launcher 實例:

sun.misc.Launcher launcher = sun.misc.Launcher.getLauncher(); //獲取Java啓動器  
ClassLoader classLoader = launcher.getClassLoader();          //獲取類加載器ClassLoader用來加載class到內存來  

啓動器作了什麼:

建立擴展類加載器和應用類加載器,並將應用加載器指定給當前線程做爲線程上下文加載器。
 public Launcher() {  
      Launcher.ExtClassLoader var1;  
      try {  
          var1 = Launcher.ExtClassLoader.getExtClassLoader();  
      } catch (IOException var10) {  
          throw new InternalError("Could not create extension class loader", var10);  
      }  
  
      try {  
          this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);  
      } catch (IOException var9) {  
          throw new InternalError("Could not create application class loader", var9);  
      }  
//將AppClassLoader設置成當前線程的上下文加載器  
      Thread.currentThread().setContextClassLoader(this.loader);  
      //.......  
}  

sun.misc.Launcher 使用了單例模式設計,保證一個JVM虛擬機內只有一個sun.misc.Launcher實例。

在Launcher的內部,其定義了兩個類加載器(ClassLoader),分別是sun.misc.Launcher.ExtClassLoader和sun.misc.Launcher.AppClassLoader,

這兩個類加載器分別被稱爲拓展類加載器(Extension ClassLoader) 和 應用類加載器(Application ClassLoader).以下圖所示:

 圖上的指向引導類加載器的虛線表示:

除了引導類加載器(Bootstrap Class Loader )外的全部類加載器,都有一個能力,就是判斷某一個類是否被引導類加載器加載過,
若是加載過,能夠直接返回對應的Class<T> instance,若是沒有,則返回null. 

 

從Launcher源碼中能夠看到

public class Launcher {  
    private static URLStreamHandlerFactory factory = new Factory();  
    private static Launcher launcher = new Launcher();  
  
    public static Launcher getLauncher() {  
        return launcher;  
    }  
  
    private ClassLoader loader;  
      
    //ClassLoader.getSystemClassLoader會調用此方法  
    public ClassLoader getClassLoader() {  
        return loader;  
    }  
  
    public Launcher() {  
        // 1. 建立ExtClassLoader   
        ClassLoader extcl;  
        try {  
            extcl = ExtClassLoader.getExtClassLoader();  
        } catch (IOException e) {  
            throw new InternalError(  
                "Could not create extension class loader");  
        }  
  
        // 2. 用ExtClassLoader做爲parent去建立AppClassLoader   
        try {  
            loader = AppClassLoader.getAppClassLoader(extcl);  
        } catch (IOException e) {  
            throw new InternalError(  
                "Could not create application class loader");  
        }  
  
        // 3. 設置AppClassLoader爲ContextClassLoader  
        Thread.currentThread().setContextClassLoader(loader);  
        //...  
    }  
  
    static class ExtClassLoader extends URLClassLoader {  
        private File[] dirs;  
  
        public static ExtClassLoader getExtClassLoader() throws IOException  
        {  
            final File[] dirs = getExtDirs();  
            return new ExtClassLoader(dirs);  
        }  
  
        public ExtClassLoader(File[] dirs) throws IOException {  
            super(getExtURLs(dirs), null, factory);  
            this.dirs = dirs;  
        }  
  
        private static File[] getExtDirs() {  
            String s = System.getProperty("java.ext.dirs");  
            File[] dirs;  
            //...  
            return dirs;  
        }  
    }  
  
    /** 
     * The class loader used for loading from java.class.path. 
     * runs in a restricted security context. 
     */  
    static class AppClassLoader extends URLClassLoader {  
  
        public static ClassLoader getAppClassLoader(final ClassLoader extcl)  
            throws IOException  
        {  
            final String s = System.getProperty("java.class.path");  
            final File[] path = (s == null) ? new File[0] : getClassPath(s);  
  
            URL[] urls = (s == null) ? new URL[0] : pathToURLs(path);  
            return new AppClassLoader(urls, extcl);  
        }  
  
        AppClassLoader(URL[] urls, ClassLoader parent) {  
            super(urls, parent, factory);  
        }  
          
        /** 
         * Override loadClass so we can checkPackageAccess. 
         * 這個方法彷佛沒什麼必要,由於super.loadClass(name, resolve)時也會checkPackageAccess 
         */  
        public synchronized Class loadClass(String name, boolean resolve)  
            throws ClassNotFoundException  
        {  
            int i = name.lastIndexOf('.');  
            if (i != -1) {  
                SecurityManager sm = System.getSecurityManager();  
                if (sm != null) {  
                    //  
                    sm.checkPackageAccess(name.substring(0, i));  
                }  
            }  
            return (super.loadClass(name, resolve));  
        }  
  
    }  
}  
View Code

launcher.getClassLoader() 方法將會返回 AppClassLoader 實例(AppClassLoader將ExtClassLoader做爲本身的父加載器)。
當AppClassLoader加載類時:

1,判斷本身是否已經加載,
2,會首先嚐試讓父加載器ExtClassLoader進行加載,若是父加載器ExtClassLoader加載成功,則AppClassLoader直接返回父加載器ExtClassLoader加載的結果;
3,若是父加載器ExtClassLoader加載失敗,AppClassLoader則會判斷該類是不是引導的系統類(便是否是經過Bootstrap類加載器加載,這會調用Native方法進行查找);
4,若要加載的類不是系統引導類,那麼ClassLoader將會嘗試本身加載,加載失敗將會拋出「ClassNotFoundException」。

再這裏就說點一個重要的概念:雙親委派模型(parent-delegation model)

對於某個特定的類加載器而言,應該爲其指定一個父類加載器,當用其進行加載類的時候:

1. 委託父類加載器幫忙加載;
2. 父類加載器加載不了,則查詢引導類加載器有沒有加載過該類;
3. 若是引導類加載器沒有加載過該類,則當前的類加載器應該本身加載該類;
4. 若加載成功,返回 對應的Class<T> 對象;若失敗,拋出異常「ClassNotFoundException」。

請注意:

雙親委派模型中的"雙親"並非指它有兩個父類加載器的意思,一個類加載器只應該有一個父加載器。

上面的步驟中,有兩個角色:

1. 父類加載器(parent classloader):它能夠替子加載器嘗試加載類
2. 引導類加載器(bootstrap classloader): 子類加載器只能判斷某個類是否被引導類加載器加載過,而不能委託它加載某個類;

4. 使用類加載器ClassLoader加載Main類

經過 launcher.getClassLoader()方法返回AppClassLoader實例,接着就是AppClassLoader加載 org.luanlouis.jvm.load.Main類

ClassLoader classloader = launcher.getClassLoader();//取得AppClassLoader類  
classLoader.loadClass("org.luanlouis.jvm.load.Main");//加載自定義類  

上述定義的org.luanlouis.jvm.load.Main類被編譯成org.luanlouis.jvm.load.Main class二進制文件,

這個class文件中有一個叫常量池(Constant Pool)的結構體來存儲該class的常亮信息。

常量池中有CONSTANT_CLASS_INFO類型的常量,表示該class中聲明瞭要用到那些類:

當AppClassLoader要加載 org.luanlouis.jvm.load.Main類時,會去查看該類的定義,
發現它內部聲明使用了其它的類:
sun.security.pkcs11.P11Util、
java.lang.Object、
java.lang.System、
java.io.PrintStream、
java.lang.Class;
org.luanlouis.jvm.load.Main類要想正常工做,首先要可以保證這些其內部聲明的類加載成功。
因此AppClassLoader要先將這些類加載到內存中。
(注:爲了理解方便,這裏沒有考慮懶加載的狀況,事實上的JVM加載類過程比這複雜的多)

加載順序:

1. 加載java.lang.Object、java.lang.System、java.io.PrintStream、java,lang.Class
   AppClassLoader嘗試加載這些類的時候,會先委託ExtClassLoader進行加載;
而ExtClassLoader發現不是其加載範圍,其返回null;
AppClassLoader發現父類加載器ExtClassLoader沒法加載,則會查詢這些類是否已經被BootstrapClassLoader加載過,
結果代表這些類已經被BootstrapClassLoader加載過,則無需重複加載,直接返回對應的Class
<T>實例; 2. 加載sun.security.pkcs11.P11Util 此在{JRE_HOME}/lib/ext/sunpkcs11.jar包內,屬於ExtClassLoader負責加載的範疇。
AppClassLoader嘗試加載這些類的時候,會先委託ExtClassLoader進行加載;
而ExtClassLoader發現其正好屬於加載範圍,故ExtClassLoader負責將其加載到內存中。
ExtClassLoader在加載sun.security.pkcs11.P11Util時也分析這個類內都使用了哪些類,
並將這些類先加載內存後,纔開始加載sun.security.pkcs11.P11Util,
加載成功後直接返回對應的Class<sun.security.pkcs11.P11Util>實例; 3. 加載org.luanlouis.jvm.load.Main AppClassLoader嘗試加載這些類的時候,會先委託ExtClassLoader進行加載;而ExtClassLoader發現不是其加載範圍,其返回null;
AppClassLoader發現父類加載器ExtClassLoader沒法加載,則會查詢這些類是否已經被BootstrapClassLoader加載過。
而結果代表BootstrapClassLoader 沒有加載過它,這時候AppClassLoader只能本身動手負責將其加載到內存中,
而後返回對應的Class
<org.luanlouis.jvm.load.Main>實例引用;

 以上三步驟都成功,才表示classLoader.loadClass("org.luanlouis.jvm.load.Main")完成,上述操做完成後,JVM內存方法區的格局會以下所示:

JVM方法區的類信息區是按照類加載器進行劃分的,每一個類加載器會維護本身加載類信息;
某個類加載器在加載相應的類時,會相應地在JVM內存堆(Heap)中建立一個對應的Class<T>,用來表示訪問該類信息的入口

5. 使用Main類的main方法做爲程序入口運行程序

6. 方法執行完畢,JVM銷燬,釋放內存

三:類加載器有哪些?其組織結構是怎樣的?

 JVM自身定義了三個類加載器:

引導類加載器(Bootstrap Class Loader)、拓展類加載器(Extension Class Loader )、應用加載器(Application Class Loader又叫系統類加載器)。

固然,咱們有時候也會本身定義一些類加載器來知足自身的須要。

1,引導類加載器(Bootstrap Class Loader): 該類加載器使JVM使用C/C++底層代碼實現的加載器,用以加載JVM運行時所須要的系統類,
這些系統類在{JRE_HOME}/lib目錄下(-Xbootclasspath選項指定)的jar包加載到內存中。
因爲引導類加載器涉及到虛擬機本地實現細節,開發者沒法直接獲取到啓動類加載器的引用。
可是,咱們能夠查詢某個類是否被引導類加載器加載過。

咱們常用的系統類如:java.lang.String,java.lang.Object,java.lang*.......
這些都被放在 {JRE_HOME}/lib/rt.jar包內,
當JVM系統啓動的時候,引導類加載器會將其加載到 JVM內存的方法區中。
2,拓展類加載器(Extension Class Loader): 該加載器是用於加載 java 的拓展類 ,
拓展類通常會放在 {JRE_HOME}
/lib/ext/ 目錄下(-Djava.ext.dir指定位置)的類庫加載到內存中,用來提供除了系統類以外的額外功能。
拓展類加載器是是整個JVM加載器的Java代碼能夠訪問到的類加載器的最頂端(便是超級父加載器,拓展類加載器是沒有父類加載器的)。
3,應用類加載器(Applocatoin Class Loader): 該類加載器是用於加載用戶代碼,是用戶代碼的入口。
負責將系統類路徑java -classpath,即環境變量classpath(或-Djava.class.path指定的目錄)的類庫加載到內存中

我常常執行指令 java xxx.x.x.XClass , 實際上,JVM就是使用的AppClassLoader加載 xxx.x.x.XClass 類的。

應用類加載器將拓展類加載器當成本身的父類加載器,當其嘗試加載類的時候,首先嚐試讓其父加載器(
拓展類加載器加載);
若是拓展類加載器加載成功,則直接返回加載結果Class<T> instance,
加載失敗,則會詢問是否引導類加載器已經加載了該類;
只有沒有加載的時候,應用類加載器纔會嘗試本身加載。

4,用戶自定義類加載器(Customized Class Loader):用戶能夠本身定義類加載器來加載類。全部的類加載器都要繼承java.lang.ClassLoader類。

因爲Class<T>是整個用戶代碼的入口,在Java虛擬機規範中,稱其爲 初始類(Initial Class).

 

 

 

四:雙親加載模型的邏輯和底層代碼實現是怎樣的?

經過JDK源碼看java.lang.ClassLoader的核心方法 loadClass()的實現:

//提供class類的二進制名稱表示,加載對應class,加載成功,則返回表示該類對應的Class<T> instance 實例  
public Class<?> loadClass(String name) throws ClassNotFoundException {  
    return loadClass(name, false);  
}  
  
  
protected Class<?> loadClass(String name, boolean resolve)  
    throws ClassNotFoundException  
{  
    synchronized (getClassLoadingLock(name)) {  
        // 首先,檢查是否已經被當前的類加載器記載過了,若是已經被加載,直接返回對應的Class<T>實例  
        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;  
    }  
}  

徹底的ClassLoader源碼:

public abstract class ClassLoader {  
  
    // 父ClassLoader  
    private ClassLoader parent;  
  
    // 被此classLoader加載過的Class對象  
    private Vector classes = new Vector();  
      
    // The packages defined in this class loader.  Each package name is mapped  
    // to its corresponding Package object.  
    private HashMap packages = new HashMap();  
  
    // 由虛擬機調用  
    void addClass(Class c) {  
        classes.addElement(c);  
    }  
  
    // The packages defined in this class loader. Each package name is mapped  
    // to its corresponding Package object.  
    private final HashMap<String, Package> packages = new HashMap<String, Package>();  
  
    // 指明parent  
    protected ClassLoader(ClassLoader parent) {  
        this(checkCreateClassLoader(), parent);  
    }  
  
    // 不指名parent時使用SystemClassLoader  
    protected ClassLoader() {  
        this(checkCreateClassLoader(), getSystemClassLoader());  
    }  
  
    // 默認resolve=false  
    public Class<?> loadClass(String name) throws ClassNotFoundException {  
        return loadClass(name, false);  
    }  
  
    protected synchronized Class<?> loadClass(String name, boolean resolve)  
            throws ClassNotFoundException {  
        // 1 檢查此class是否被此classloader加載過,  
        // 最終是有native方法返回,native方法會使用到classes集合  
        Class c = findLoadedClass(name);  
        // 1.1 未被加載  
        if (c == null) {  
            try {  
                // 1.1.1 此classloader有parent,委託parent去load  
                if (parent != null) {  
                    c = parent.loadClass(name, false);  
                } else {// 1.1.2 此classloader無parent = 啓動類裝載器去加載  
                    c = findBootstrapClassOrNull(name);  
                }  
            } catch (ClassNotFoundException e) {  
            }  
            // 若是沒有找到class,本身去加載試試  
            if (c == null) {  
                c = findClass(name);  
            }  
        }  
        // 找到的Class對象是否須要鏈接操做  
        if (resolve) {  
            resolveClass(c);  
        }  
        // 1.2 被加載過,直接返回  
        return c;  
    }  
  
    protected final Class<?> findLoadedClass(String name) {  
        if (!checkName(name))  
            return null;  
        return findLoadedClass0(name);  
    }  
  
    // 若是name裏包含/,或者虛擬機不支持class數組你使用了數組的時候返回false,其它狀況返回true,包括空的name  
    // eg com.jyz.component.core.collection.Tuple return true  
    private boolean checkName(String name) {  
        if ((name == null) || (name.length() == 0))  
            return true;  
        if ((name.indexOf('/') != -1)  
                || (!VM.allowArraySyntax() && (name.charAt(0) == '[')))  
            return false;  
        return true;  
    }  
  
    // 檢查package是否可訪問  
    private void checkPackageAccess(Class cls, ProtectionDomain pd) {  
        final SecurityManager sm = System.getSecurityManager();  
        if (sm != null) {  
            // ...  
        }  
        domains.add(pd);  
    }  
  
    // 自定義classloader時重寫此方法  
    protected Class<?> findClass(String name) throws ClassNotFoundException {  
        throw new ClassNotFoundException(name);  
    }  
  
    // 將byte數組轉換成一個Class對象,最終有native方法實現  
    // 同一個byte數組,被不一樣的classloader加載,產生兩個不一樣的Class對象  
    protected final Class<?> defineClass(String name, byte[] b, int off, int len)  
            throws ClassFormatError {  
        return defineClass(name, b, off, len, null);  
    }  
  
    /* 
     * Determine protection domain, and check that: - not define java.* class, - 
     * signer of this class matches signers for the rest of the classes in 
     * package. 
     */  
    //native的defineClass時會調用此方法檢查name是否合法  
    //首先checkName,而後還須要!name.startsWith("java.")  
    //因此咱們定義了java.mypackage包,都將異常  
    //java.lang.SecurityException: Prohibited package name: java.mypackage  
    private ProtectionDomain preDefineClass(String name,  
            ProtectionDomain protectionDomain) {  
        if (!checkName(name))  
            throw new NoClassDefFoundError("IllegalName: " + name);  
        if ((name != null) && name.startsWith("java.")) {  
            throw new SecurityException("Prohibited package name: "  
                    + name.substring(0, name.lastIndexOf('.')));  
        }  
        //...  
    }  
  
    // protected的resolveClass方法,能夠在自定義的classloader調用  
    protected final void resolveClass(Class<?> c) {  
        resolveClass0(c);  
    }  
      
    //得到appClassLoader,實際調用Launcher完成  
    public static ClassLoader getSystemClassLoader() {  
        sun.misc.Launcher l = sun.misc.Launcher.getLauncher();  
        return l.getClassLoader();  
    }  
  
} 
View Code

五:類加載器與Class<T>  實例的關係

六:線程上下文加載器

咱們平時並無使用加載器,是由於jvm幫我調用了。(會將加載器關聯到線程上)

 Java 任何一段代碼的執行,都有對應的線程上下文。若是咱們在代碼中,想看當前是哪個線程在執行當前代碼,咱們常常是使用以下方法:

Thread  thread = Thread.currentThread();//返回對當當前運行線程的引用  

相應地,咱們能夠爲當前的線程指定類加載器。

在上述的例子中, 當執行   java    org.luanlouis.jvm.load.Main  的時候,JVM會建立一個Main線程,而建立應用類加載器AppClassLoader的時候,

會將AppClassLoader  設置成Main線程的上下文類加載器:

 public Launcher() {  
      Launcher.ExtClassLoader var1;  
      try {  
          var1 = Launcher.ExtClassLoader.getExtClassLoader();  
      } catch (IOException var10) {  
          throw new InternalError("Could not create extension class loader", var10);  
      }  
  
      try {  
          this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);  
      } catch (IOException var9) {  
          throw new InternalError("Could not create application class loader", var9);  
      }  
//將AppClassLoader設置成當前線程的上下文加載器  
      Thread.currentThread().setContextClassLoader(this.loader);  
      //.......  
  }  

線程上下文類加載器是從線程的角度來看待類的加載,爲每個線程綁定一個類加載器,能夠將類的加載從單純的 雙親加載模型解放出來,進而實現特定的加載需求。

線程上下文加載器的意義何在???

 

七:擴展加載器和應用加載器的繼承結構:

特別注意的是:雖然都是URLClassLoader的子類,可是都是Launcher類的靜態內部類,jdk的api是沒有的。

都是sun/misc/Launcher.class的靜態成員內部類,聲明以下:
static class AppClassLoader extends URLClassLoader
static class ExtClassLoader extends URLClassLoader

 

八:類加載器加載路徑的測試 

Launcher類的部分源碼(比較老的一個版本),主要是引導類加載器加載的路徑

private static Launcher launcher = new Launcher();  
private static String bootClassPath =  System.getProperty("sun.boot.class.path");  
  
    public static Launcher getLauncher() {  
        return launcher;  
    }  
  
    private ClassLoader loader;  
  
    public Launcher() {  
        // Create the extension class loader  
        ClassLoader extcl;  
        try {  
            extcl = ExtClassLoader.getExtClassLoader();  
        } catch (IOException e) {  
            throw new InternalError(  
                "Could not create extension class loader");  
        }  
  
        // Now create the class loader to use to launch the application  
        try {  
            loader = AppClassLoader.getAppClassLoader(extcl);  
        } catch (IOException e) {  
            throw new InternalError(  
                "Could not create application class loader");  
        }  
  
        // Also set the context class loader for the primordial thread.  
        Thread.currentThread().setContextClassLoader(loader);  
  
        // Finally, install a security manager if requested  
        String s = System.getProperty("java.security.manager");  
        ......  
    }  
  
    /* 
     * Returns the class loader used to launch the main application. 
     */  
    public ClassLoader getClassLoader() {  
        return loader;  
    }  
View Code

測試:

public static void main(String[] args) throws Exception {  
          
        ClassLoader appClassLoader = ClassLoader.getSystemClassLoader();  
        ClassLoader extClassloader = appClassLoader.getParent();  
        ClassLoader bootstrapLoader  = extClassloader.getParent();  
        System.out.println("the bootstrapLoader : " + bootstrapLoader);  
        System.out.println("the extClassloader : " + extClassloader);  
        System.out.println("the appClassLoader : " + appClassLoader);  
          
        System.out.println();  
        System.out.println("bootstrapLoader加載如下文件:");  
        URL[] urls = Launcher.getBootstrapClassPath().getURLs();  
        for (int i = 0; i < urls.length; i++) {  
            System.out.println(urls[i]);  
        }  
          
        System.out.println();  
        System.out.println("extClassloader加載如下文件:");  
        System.out.println(System.getProperty("java.ext.dirs"));  
          
        System.out.println();  
        System.out.println("appClassLoader加載如下文件:");  
        System.out.println(System.getProperty("java.class.path"));  
          
    }  
}  

 eclipse下測試:

dos下測試:

總結:

能夠發現應用類加載器加載就是環境變量classpath設置的。
(至於eclipse中不一樣,是由於eclipse臨時修改了本身的classpath。)

九:測試加載器加載類的路徑是被限制的:

默認引導類加載器只會加載{jre.lib}下的,而且還作了限制只會加載系統類。
默認擴展類加載器只會加載{jre.ext.lib}下的。
默認系統類加載器(應用類加載器)只會加載classpath下的。

1,在同一目錄中創建2個類:

須要加載的類:
public class TestBean {        
    public TestBean() { }  
}  
測試類:
public class ClassLoaderTest { public static void main(String[] args) { try { //調用加載當前類的類加載器(這裏即爲系統類加載器)加載TestBean Class typeLoaded = Class.forName("TestBean"); //查看被加載的TestBean類型是被那個類加載器加載的 System.out.println(typeLoaded.getClassLoader()); } catch (Exception e) { e.printStackTrace(); } } }

輸出:(由於eclipse默認classpath是當前工程下)補充:eclipse的classpath(build path)和classpaht幾種設置的方式

sun.misc.Launcher$AppClassLoader@73d16e93  

2,將TestBean.class複製打包進test.jar剪貼到<JRE_Home>/lib/ext目錄下(如今工程目錄下和JRE擴展目錄下都有待加載類型的class文件)。

sun.misc.Launcher$ExtClassLoader@15db9742  

對比測試一和測試二,能夠驗證前面說的雙親委派機制:

系統類加載器在接到加載TestBean類型的請求時,首先將請求委派給父類加載器(標準擴展類加載器),標準擴展類加載器搶先完成了加載請求。

 

3,將test.jar拷貝一份到<JRE_Home>/lib下,運行測試代碼:

sun.misc.Launcher$ExtClassLoader@15db9742  

放置到<JRE_Home>/lib目錄下的TestBean對應的class字節碼並無被加載,這其實和前面講的雙親委派機制並不矛盾。

虛擬機出於安全等因素考慮,不會加載<JRE_Home>/lib存在的陌生類,不會加載非JDK自身的類。

若是將工程和jre/lib/ext下的TestBean移除,只保留jre/lib下的,會發現findBootstrapClass0()會拋出異常,找不到類。

相關文章
相關標籤/搜索