4. JDK源碼級別徹底剖析類加載機(jī)制

類加載運行全過程

當(dāng)我們用java命令運行某個類的main函數(shù)啟動程序時,首先需要通過類加載器把主類加載到 JVM。

package com.tuling.jvm;

/**
 * Description: <br/>
 *
 * @Date: 2021/2/5 15:34<br/>
 * @Author Cong ZhiZzhi<br/>
 * @Version
 * @Since JDK 1.8
 */
public class Math {


    public static final int initData = 666;
    public static User user = new User();

    public int compute() {
        int a = 1;
        int b = 2;
        int c = (a + b) * 10;
        return c;
    }

    public static void main(String[] args) {
        Math math = new Math();
        math.compute();
    }
}

通過Java命令執(zhí)行代碼的大體流程如下:



其中l(wèi)oadClass的類加載過程有如下幾步:
加載 >> 驗證 >> 準(zhǔn)備 >> 解析 >> 初始化 >> 使用 >> 卸載

  • 加載:在硬盤上查找并通過IO讀入字節(jié)碼文件,使用到類時才會加載,例如調(diào)用類的 main()方法,new對象等等,在加載階段會在內(nèi)存中生成一個代表這個類的 java.lang.Class對象,作為方法區(qū)這個類的各種數(shù)據(jù)的訪問入口
  • 驗證:校驗字節(jié)碼文件的正確性
  • 準(zhǔn)備:給類的靜態(tài)變量分配內(nèi)存,并賦予默認(rèn)值
  • 解析:將符號引用替換為直接引用,該階段會把一些靜態(tài)方法(符號引用,比如 main()方法)替換為指向數(shù)據(jù)所存內(nèi)存的指針或句柄等(直接引用),這是所謂的靜態(tài)鏈接過程(類加載期間完成),動態(tài)鏈接是在程序運行期間完成的將符號引用替換為直接引用
  • 初始化:對類的靜態(tài)變量初始化為指定的值,執(zhí)行靜態(tài)代碼塊


類被加載到方法區(qū)中后主要包含運行時常量池類型信息字段信息方法信息類加載器的引用對應(yīng)class實例的引用等信息。
類加載器的引用:這個類到類加載器實例的引用
對應(yīng)class實例的引用:類加載器在加載類信息放到方法區(qū)中后,會創(chuàng)建一個對應(yīng)的Class 類型的對象實例放到堆(Heap)中, 作為開發(fā)人員訪問方法區(qū)中類定義的入口和切入點。

注意,主類在運行過程中如果使用到其它類,會逐步加載這些類。 jar包或war包里的類不是一次性全部加載的,是使用到時才加載。

public class TestDynamicLoad {
    static {
        System.out.println("*************load TestDynamicLoad************");
    }

    public static void main(String[] args) {
        new A();
        System.out.println("*************load test************");
        B b = null; //B不會加載,除非這里執(zhí)行 new B() 11 }
    }
}

class A {
    static {
        System.out.println("*************load A************");
    }

    public A() {
        System.out.println("*************initial A************");
    }
}

class B {
    static {
        System.out.println("*************load B************");
    }

    public B() {
        System.out.println("*************initial B************");
    }
}

類加載器和雙親委派機(jī)制

上面的類加載過程主要是通過類加載器來實現(xiàn)的,Java里有如下幾種類加載器

  • 引導(dǎo)類加載器:負(fù)責(zé)加載支撐JVM運行的位于JRE的lib目錄下的核心類庫,比如 rt.jar、charsets.jar等
  • 擴(kuò)展類加載器:負(fù)責(zé)加載支撐JVM運行的位于JRE的lib目錄下的ext擴(kuò)展目錄中的JAR 類包
  • 應(yīng)用程序類加載器:負(fù)責(zé)加載ClassPath路徑下的類包,主要就是加載你自己寫的那 些類
  • 自定義加載器:負(fù)責(zé)加載用戶自定義路徑下的類包
public class TestJDKClassLoader {


    public static void main(String[] args) {

        System.out.println(String.class.getClassLoader());
        System.out.println(com.sun.crypto.provider.DESKeyFactory.class.getClassLoader().getClass().getName());
        System.out.println(TestJDKClassLoader.class.getClassLoader().getClass().getName());
        System.out.println();


        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"));
    }
}

運行結(jié)果:

D:\develop\jdk1.8.0_201\bin\java.exe "-javaagent:C:\Program Files\JetBrains\IntelliJ IDEA 2019.1.3\lib\idea_rt.jar=58231:C:\Program Files\JetBrains\IntelliJ IDEA 2019.1.3\bin" -Dfile.encoding=UTF-8 -classpath D:\develop\jdk1.8.0_201\jre\lib\charsets.jar;D:\develop\jdk1.8.0_201\jre\lib\deploy.jar;D:\develop\jdk1.8.0_201\jre\lib\ext\access-bridge-64.jar;D:\develop\jdk1.8.0_201\jre\lib\ext\cldrdata.jar;D:\develop\jdk1.8.0_201\jre\lib\ext\dnsns.jar;D:\develop\jdk1.8.0_201\jre\lib\ext\jaccess.jar;D:\develop\jdk1.8.0_201\jre\lib\ext\jfxrt.jar;D:\develop\jdk1.8.0_201\jre\lib\ext\localedata.jar;D:\develop\jdk1.8.0_201\jre\lib\ext\nashorn.jar;D:\develop\jdk1.8.0_201\jre\lib\ext\sunec.jar;D:\develop\jdk1.8.0_201\jre\lib\ext\sunjce_provider.jar;D:\develop\jdk1.8.0_201\jre\lib\ext\sunmscapi.jar;D:\develop\jdk1.8.0_201\jre\lib\ext\sunpkcs11.jar;D:\develop\jdk1.8.0_201\jre\lib\ext\zipfs.jar;D:\develop\jdk1.8.0_201\jre\lib\javaws.jar;D:\develop\jdk1.8.0_201\jre\lib\jce.jar;D:\develop\jdk1.8.0_201\jre\lib\jfr.jar;D:\develop\jdk1.8.0_201\jre\lib\jfxswt.jar;D:\develop\jdk1.8.0_201\jre\lib\jsse.jar;D:\develop\jdk1.8.0_201\jre\lib\management-agent.jar;D:\develop\jdk1.8.0_201\jre\lib\plugin.jar;D:\develop\jdk1.8.0_201\jre\lib\resources.jar;D:\develop\jdk1.8.0_201\jre\lib\rt.jar;D:\codespace\jvm\target\classes;D:\develop\apache-maven-3.6.1\repository\log4j\log4j\1.2.17\log4j-1.2.17.jar;D:\develop\apache-maven-3.6.1\repository\org\projectlombok\lombok\1.16.18\lombok-1.16.18.jar com.tuling.jvm.TestJDKClassLoader
null
sun.misc.Launcher$ExtClassLoader
sun.misc.Launcher$AppClassLoader

the bootstrapLoader : null
the extClassloader : sun.misc.Launcher$ExtClassLoader@5cad8086
the appClassLoader : sun.misc.Launcher$AppClassLoader@18b4aac2

bootstrapLoader加載以下文件:
file:/D:/develop/jdk1.8.0_201/jre/lib/resources.jar
file:/D:/develop/jdk1.8.0_201/jre/lib/rt.jar
file:/D:/develop/jdk1.8.0_201/jre/lib/sunrsasign.jar
file:/D:/develop/jdk1.8.0_201/jre/lib/jsse.jar
file:/D:/develop/jdk1.8.0_201/jre/lib/jce.jar
file:/D:/develop/jdk1.8.0_201/jre/lib/charsets.jar
file:/D:/develop/jdk1.8.0_201/jre/lib/jfr.jar
file:/D:/develop/jdk1.8.0_201/jre/classes

extClassloader加載以下文件:
D:\develop\jdk1.8.0_201\jre\lib\ext;C:\Windows\Sun\Java\lib\ext

appClassLoader加載以下文件:
D:\develop\jdk1.8.0_201\jre\lib\charsets.jar;D:\develop\jdk1.8.0_201\jre\lib\deploy.jar;D:\develop\jdk1.8.0_201\jre\lib\ext\access-bridge-64.jar;D:\develop\jdk1.8.0_201\jre\lib\ext\cldrdata.jar;D:\develop\jdk1.8.0_201\jre\lib\ext\dnsns.jar;D:\develop\jdk1.8.0_201\jre\lib\ext\jaccess.jar;D:\develop\jdk1.8.0_201\jre\lib\ext\jfxrt.jar;D:\develop\jdk1.8.0_201\jre\lib\ext\localedata.jar;D:\develop\jdk1.8.0_201\jre\lib\ext\nashorn.jar;D:\develop\jdk1.8.0_201\jre\lib\ext\sunec.jar;D:\develop\jdk1.8.0_201\jre\lib\ext\sunjce_provider.jar;D:\develop\jdk1.8.0_201\jre\lib\ext\sunmscapi.jar;D:\develop\jdk1.8.0_201\jre\lib\ext\sunpkcs11.jar;D:\develop\jdk1.8.0_201\jre\lib\ext\zipfs.jar;D:\develop\jdk1.8.0_201\jre\lib\javaws.jar;D:\develop\jdk1.8.0_201\jre\lib\jce.jar;D:\develop\jdk1.8.0_201\jre\lib\jfr.jar;D:\develop\jdk1.8.0_201\jre\lib\jfxswt.jar;D:\develop\jdk1.8.0_201\jre\lib\jsse.jar;D:\develop\jdk1.8.0_201\jre\lib\management-agent.jar;D:\develop\jdk1.8.0_201\jre\lib\plugin.jar;D:\develop\jdk1.8.0_201\jre\lib\resources.jar;D:\develop\jdk1.8.0_201\jre\lib\rt.jar;D:\codespace\jvm\target\classes;D:\develop\apache-maven-3.6.1\repository\log4j\log4j\1.2.17\log4j-1.2.17.jar;D:\develop\apache-maven-3.6.1\repository\org\projectlombok\lombok\1.16.18\lombok-1.16.18.jar;C:\Program Files\JetBrains\IntelliJ IDEA 2019.1.3\lib\idea_rt.jar

類加載器初始化過程:

參見類運行加載全過程圖可知其中會創(chuàng)建JVM啟動器實例sun.misc.Launcher。
sun.misc.Launcher初始化使用了單例模式設(shè)計,保證一個JVM虛擬機(jī)內(nèi)只有一個 sun.misc.Launcher實例。
在Launcher構(gòu)造方法內(nèi)部,其創(chuàng)建了兩個類加載器,分別是 sun.misc.Launcher.ExtClassLoader(擴(kuò)展類加載器)和sun.misc.Launcher.AppClassLoader(應(yīng) 用類加載器)。
JVM默認(rèn)使用Launcher的getClassLoader()方法返回的類加載器AppClassLoader的實例加載我們 的應(yīng)用程序。

1 //Launcher的構(gòu)造方法 
2 public Launcher() {
3 Launcher.ExtClassLoader var1; 
4 try { 
5 //構(gòu)造擴(kuò)展類加載器,在構(gòu)造的過程中將其父加載器設(shè)置為null 
6 var1 = Launcher.ExtClassLoader.getExtClassLoader(); 
7 } catch (IOException var10) { 
8 throw new InternalError("Could not create extension class loader", var10); 
9 } 
10
11 try { 
12 //構(gòu)造應(yīng)用類加載器,在構(gòu)造的過程中將其父加載器設(shè)置為ExtClassLoader, 
13 //Launcher的loader屬性值是AppClassLoader,我們一般都是用這個類加載器來加載我們自 己寫的應(yīng)用程序 
14 this.loader = Launcher.AppClassLoader.getAppClassLoader(var1); 
15 } catch (IOException var9) { 
16 throw new InternalError("Could not create application class loader", var9); 
17 } 
18
19 Thread.currentThread().setContextClassLoader(this.loader); 
20 String var2 = System.getProperty("java.security.manager"); 
21 。。。 。。。 //省略一些不需關(guān)注代碼 
22 }

雙親委派機(jī)制

JVM類加載器是有親子層級結(jié)構(gòu)的,如下圖



這里類加載其實就有一個雙親委派機(jī)制,加載某個類時會先委托父加載器尋找目標(biāo)類,找不到再 委托上層父加載器加載,如果所有父加載器在自己的加載類路徑下都找不到目標(biāo)類,則在自己的 類加載路徑中查找并載入目標(biāo)類。
比如我們的Math類,最先會找應(yīng)用程序類加載器加載,應(yīng)用程序類加載器會先委托擴(kuò)展類加載 器加載,擴(kuò)展類加載器再委托引導(dǎo)類加載器,頂層引導(dǎo)類加載器在自己的類加載路徑里找了半天 沒找到Math類,則向下退回加載Math類的請求,擴(kuò)展類加載器收到回復(fù)就自己加載,在自己的類加載路徑里找了半天也沒找到Math類,又向下退回Math類的加載請求給應(yīng)用程序類加載器, 應(yīng)用程序類加載器于是在自己的類加載路徑里找Math類,結(jié)果找到了就自己加載了。。

雙親委派機(jī)制說簡單點就是,先找父親加載,不行再由兒子自己加載

我們來看下應(yīng)用程序類加載器AppClassLoader加載類的雙親委派機(jī)制源碼,AppClassLoader 的loadClass方法最終會調(diào)用其父類ClassLoader的loadClass方法,該方法的大體邏輯如下:

  1. 首先,檢查一下指定名稱的類是否已經(jīng)加載過,如果加載過了,就不需要再加載,直接 返回。
  2. 如果此類沒有加載過,那么,再判斷一下是否有父加載器;如果有父加載器,則由父加 載器加載(即調(diào)用parent.loadClass(name, false);).或者是調(diào)用bootstrap類加載器來加 載。
  3. 如果父加載器及bootstrap類加載器都沒有找到指定的類,那么調(diào)用當(dāng)前類加載器的 findClass方法來完成類加載。
1 //ClassLoader的loadClass方法,里面實現(xiàn)了雙親委派機(jī)制 
2 protected Class<?> loadClass(String name, boolean resolve) 
3 throws ClassNotFoundException 
4 { 
5 synchronized (getClassLoadingLock(name)) { 
6 // 檢查當(dāng)前類加載器是否已經(jīng)加載了該類 
7 Class<?> c = findLoadedClass(name); 
8 if (c == null) { 
9 long t0 = System.nanoTime(); 
10 try { 
11 if (parent != null) { //如果當(dāng)前加載器父加載器不為空則委托父加載器加載該類 
12 c = parent.loadClass(name, false); 
13 } else { //如果當(dāng)前加載器父加載器為空則委托引導(dǎo)類加載器加載該類 
14 c = findBootstrapClassOrNull(name); 
15 } 
16 } catch (ClassNotFoundException e) { 
17 // ClassNotFoundException thrown if class not found 
18 // from the non‐null parent class loader 
19 } 20
21 if (c == null) { 
22 // If still not found, then invoke findClass in order 
23 // to find the class. 
24 long t1 = System.nanoTime(); 
25 //都會調(diào)用URLClassLoader的findClass方法在加載器的類路徑里查找并加載該類 
26 c = findClass(name); 
27
28 // this is the defining class loader; record the stats 
29 sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 ‐ t0); 
30 sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1); 
31 sun.misc.PerfCounter.getFindClasses().increment(); 
32 } 
33 } 
34 if (resolve) { //不會執(zhí)行 
35 resolveClass(c); 
36 } 
37 return c; 
38 } 
39 }

為什么要設(shè)計雙親委派機(jī)制?

  • 沙箱安全機(jī)制:自己寫的java.lang.String.class類不會被加載,這樣便可以防止核心 API庫被隨意篡改
  • 避免類的重復(fù)加載:當(dāng)父親已經(jīng)加載了該類時,就沒有必要子ClassLoader再加載一 次,保證被加載類的唯一性
    看一個類加載示例:
1package java.lang; 
2
3 public class String { 
4 public static void main(String[] args) { 
5 System.out.println("**************My String Class**************"); 
6 } 
7 } 
8
9 運行結(jié)果: 
10 錯誤: 在類 java.lang.String 中找不到 main 方法, 請將 main 方法定義為: 
11 public static void main(String[] args) 
12 否則 JavaFX 應(yīng)用程序類必須擴(kuò)展javafx.application.Application

全盤負(fù)責(zé)委托機(jī)制

“全盤負(fù)責(zé)”是指當(dāng)一個ClassLoder裝載一個類時,除非顯示的使用另外一個ClassLoder,該類 所依賴及引用的類也由這個ClassLoder載入。

自定義類加載器示例:

自定義類加載器只需要繼承 java.lang.ClassLoader 類,該類有兩個核心方法,一個是 loadClass(String, boolean),實現(xiàn)了雙親委派機(jī)制,還有一個方法是findClass,默認(rèn)實現(xiàn)是空 方法,所以我們自定義類加載器主要是重寫findClass方法。

public class MyClassLoaderTest {

    static class MyClassLoader extends ClassLoader {
        private String classPath;

        public MyClassLoader(String classPath) {
            this.classPath = classPath;
        }

        private byte[] loadByte(String name) throws Exception {
            name = name.replaceAll("\\.", "/");
            FileInputStream fis = new FileInputStream(classPath + "/" + name + ".class");
            int len = fis.available();
            byte[] data = new byte[len];
            fis.read(data);
            fis.close();
            return data;
        }


        @Override
        protected Class<?> findClass(String name) throws ClassNotFoundException {
            try {
                byte[] data = loadByte(name);
                //defineClass將一個字節(jié)數(shù)組轉(zhuǎn)為Class對象,這個字節(jié)數(shù)組是class文件讀取后最終的字節(jié) 數(shù)組。
                return defineClass(name, data, 0, data.length);
            } catch (Exception e) {
                e.printStackTrace();
                throw new ClassNotFoundException();
            }
        }


    }

    public static void main(String[] args) throws Exception {
        //初始化自定義類加載器,會先初始化父類ClassLoader,其中會把自定義類加載器的父加載 器設(shè)置為應(yīng)用程序類加載器AppClassLoader
        MyClassLoader classLoader = new MyClassLoader("D:/codespace/test");
        //D盤創(chuàng)建 test/com/tuling/jvm 幾級目錄,將User類的復(fù)制類User1.class丟入該目錄
        Class clazz = classLoader.loadClass("com.congzhizhi.classloader.User");
        Object obj = clazz.newInstance();
        Method method = clazz.getDeclaredMethod("getName", null);

        System.out.println(method.invoke(obj, null));
        System.out.println(clazz.getClassLoader().getClass().getName());
    }
}

打印結(jié)果


打破雙親委派機(jī)制

再來一個沙箱安全機(jī)制示例,嘗試打破雙親委派機(jī)制,用自定義類加載器加載我們自己實現(xiàn)的 java.lang.String.class

public class MyClassLoaderTest {

    static class MyClassLoader extends ClassLoader {
        private String classPath;

        public MyClassLoader(String classPath) {
            this.classPath = classPath;
        }

        private byte[] loadByte(String name) throws Exception {
            name = name.replaceAll("\\.", "/");
            FileInputStream fis = new FileInputStream(classPath + "/" + name + ".class");
            int len = fis.available();
            byte[] data = new byte[len];
            fis.read(data);
            fis.close();
            return data;
        }


        @Override
        protected Class<?> findClass(String name) throws ClassNotFoundException {
            try {
                byte[] data = loadByte(name);
                //defineClass將一個字節(jié)數(shù)組轉(zhuǎn)為Class對象,這個字節(jié)數(shù)組是class文件讀取后最終的字節(jié) 數(shù)組。
                return defineClass(name, data, 0, data.length);
            } catch (Exception e) {
                e.printStackTrace();
                throw new ClassNotFoundException();
            }
        }


        @Override
        protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
            synchronized (getClassLoadingLock(name)) {
                // First, check if the class has already been loaded
                Class<?> c = findLoadedClass(name);
                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.getFindClassTime().addElapsedTimeFrom(t1);
                    sun.misc.PerfCounter.getFindClasses().increment();
                }
                if (resolve) {
                    resolveClass(c);
                }
                return c;
            }
        }
    }

    public static void main(String[] args) throws Exception {
        //初始化自定義類加載器,會先初始化父類ClassLoader,其中會把自定義類加載器的父加載 器設(shè)置為應(yīng)用程序類加載器AppClassLoader
        MyClassLoader classLoader = new MyClassLoader("D:/codespace/test");
        //D盤創(chuàng)建 test/com/tuling/jvm 幾級目錄,將User類的復(fù)制類User1.class丟入該目錄
        Class clazz = classLoader.loadClass("java.lang.String");
        Object obj = clazz.newInstance();
        Method method = clazz.getDeclaredMethod("sout", null);

        method.invoke(obj, null);
        System.out.println(clazz.getClassLoader().getClass().getName());
    }
}

//運行結(jié)果



哈哈哈,想嘗試加載JDK的類,歇著吧您內(nèi)!

Tomcat打破雙親委派機(jī)制

以Tomcat類加載為例,Tomcat 如果使用默認(rèn)的雙親委派類加載機(jī)制行不行? 我們思考一下:Tomcat是個web容器, 那么它要解決什么問題:

  1. 一個web容器可能需要部署兩個應(yīng)用程序,不同的應(yīng)用程序可能會依賴同一個第三方類庫的 不同版本,不能要求同一個類庫在同一個服務(wù)器只有一份,因此要保證每個應(yīng)用程序的類庫都是 獨立的,保證相互隔離。 2. 部署在同一個web容器中相同的類庫相同的版本可以共享。否則,如果服務(wù)器有10個應(yīng)用程 序,那么要有10份相同的類庫加載進(jìn)虛擬機(jī)。
  2. web容器也有自己依賴的類庫,不能與應(yīng)用程序的類庫混淆。基于安全考慮,應(yīng)該讓容器的 類庫和程序的類庫隔離開來。
  3. web容器要支持jsp的修改,我們知道,jsp 文件最終也是要編譯成class文件才能在虛擬機(jī)中 運行,但程序運行后修改jsp已經(jīng)是司空見慣的事情, web容器需要支持 jsp 修改后不用重啟。

再看看我們的問題:Tomcat 如果使用默認(rèn)的雙親委派類加載機(jī)制行不行?
答案是不行的。為什么?
第一個問題,如果使用默認(rèn)的類加載器機(jī)制,那么是無法加載兩個相同類庫的不同版本的,默認(rèn) 的類加器是不管你是什么版本的,只在乎你的全限定類名,并且只有一份。
第二個問題,默認(rèn)的類加載器是能夠?qū)崿F(xiàn)的,因為他的職責(zé)就是保證唯一性。
第三個問題和第一個問題一樣。
我們再看第四個問題,我們想我們要怎么實現(xiàn)jsp文件的熱加載,jsp 文件其實也就是class文 件,那么如果修改了,但類名還是一樣,類加載器會直接取方法區(qū)中已經(jīng)存在的,修改后的jsp 是不會重新加載的。那么怎么辦呢?我們可以直接卸載掉這jsp文件的類加載器,所以你應(yīng)該想 到了,每個jsp文件對應(yīng)一個唯一的類加載器,當(dāng)一個jsp文件修改了,就直接卸載這個jsp類加載 器。重新創(chuàng)建類加載器,重新加載jsp文件。


tomcat的幾個主要類加載器:

  • commonLoader:Tomcat最基本的類加載器,加載路徑中的class可以被Tomcat容 器本身以及各個Webapp訪問;
  • catalinaLoader:Tomcat容器私有的類加載器,加載路徑中的class對于Webapp不 可見;
  • sharedLoader:各個Webapp共享的類加載器,加載路徑中的class對于所有 Webapp可見,但是對于Tomcat容器不可見;
  • WebappClassLoader:各個Webapp私有的類加載器,加載路徑中的class只對當(dāng)前 Webapp可見,比如加載war包里相關(guān)的類,每個war包應(yīng)用都有自己的WebappClassLoader,實現(xiàn)相互隔離,比如不同war包應(yīng)用引入了不同的spring版本, 這樣實現(xiàn)就能加載各自的spring版本;

從圖中的委派關(guān)系中可以看出:
CommonClassLoader能加載的類都可以被CatalinaClassLoader和SharedClassLoader使用, 從而實現(xiàn)了公有類庫的共用,而CatalinaClassLoader和SharedClassLoader自己能加載的類則與對方相互隔離。
WebAppClassLoader可以使用SharedClassLoader加載到的類,但各個WebAppClassLoader 實例之間相互隔離。 而JasperLoader的加載范圍僅僅是這個JSP文件所編譯出來的那一個.Class文件,它出現(xiàn)的目的 就是為了被丟棄:當(dāng)Web容器檢測到JSP文件被修改時,會替換掉目前的JasperLoader的實例, 并通過再建立一個新的Jsp類加載器來實現(xiàn)JSP文件的熱加載功能。

tomcat 這種類加載機(jī)制違背了java 推薦的雙親委派模型了嗎?答案是:違背了。 很顯然,tomcat 不是這樣實現(xiàn),tomcat 為了實現(xiàn)隔離性,沒有遵守這個約定,每個 webappClassLoader加載自己的目錄下的class文件,不會傳遞給父類加載器,打破了雙親委派機(jī)制。

模擬實現(xiàn)Tomcat的webappClassLoader加載自己war包應(yīng)用內(nèi)不同版本類實現(xiàn)相互共存與隔 離

public static void main(String args[]) throws Exception { 
 MyClassLoader classLoader = new MyClassLoader("D:/test"); 
 Class clazz = classLoader.loadClass("com.tuling.jvm.User1"); 
 Object obj = clazz.newInstance(); 
 Method method= clazz.getDeclaredMethod("sout", null); 
 method.invoke(obj, null); 
 System.out.println(clazz.getClassLoader()); 
System.out.println(); 
MyClassLoader classLoader1 = new MyClassLoader("D:/test1"); 
Class clazz1 = classLoader1.loadClass("com.tuling.jvm.User1"); 
Object obj1 = clazz1.newInstance();  Method method1= clazz1.getDeclaredMethod("sout", null);  method1.invoke(obj1, null); 
 System.out.println(clazz1.getClassLoader());
}

注意:同一個JVM內(nèi),兩個相同包名和類名的類對象可以共存,因為他們的類加載器可以不一 樣,所以看兩個類對象是否是同一個,除了看類的包名和類名是否都相同之外,還需要他們的類加載器也是同一個才能認(rèn)為他們是同一個。

`

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,663評論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,125評論 3 414
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,506評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,614評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 71,402評論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 54,934評論 1 321
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,021評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,168評論 0 287
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,690評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,596評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,784評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,288評論 5 357
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,027評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,404評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,662評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,398評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 47,743評論 2 370

推薦閱讀更多精彩內(nèi)容