【心得分享】-- 類的加載機制深度解析

1. 類的加載機制深度解析

1.1 類的加載運行過程

在這裏插入圖片描述

如上圖所示,Java中的類的加載靠的是類加載器實現的,其中通過loadClass進行類加載的時候會經歷:加載》》驗證》》準備》》解析》》初始化幾個步驟。

  • 加載:查找磁盤中的類的字節碼文件並通過io操作讀入,只有類被真正使用的時候纔會被加載
  • 驗證:驗證字節碼是否符合規範
  • 準備:爲靜態變量分配內存空間,並且賦上默認的初始值。int類型賦值0、boolean類型賦值false、對象類型賦值null
  • 解析:將符號引用(靜態方法)轉換爲直接引用
  • 初始化:爲靜態變量賦上真正的值

在這裏插入圖片描述

注意:主類在運行時如果使用到其他類,這些類會被逐步加載,jar包和war包中的類並不是一次性都加載出來的,而是真正使用到這個類的時候纔會進行加載

package com.muzili.jvm;

public class TestDynamicLoad {

    public static String initDate="test";

    static {
        System.out.println("運行TestDynamicLoad類的靜態方法.....");
    }

    public TestDynamicLoad(){
        System.out.println("運行TestDynamicLoad類的構造方法.....");
    }


    public static class InnerClassA{
        static {
            System.out.println("運行InnerClassA類的靜態方法.....");
        }

        public InnerClassA(){
            System.out.println("運行InnerClassA類的構造方法.....");
        }
    }

    public static void main(String[] args) {
        TestDynamicLoad testDynamicLoad=new TestDynamicLoad();

        InnerClassA innerClassA=new InnerClassA();
    }
}

結果輸出:
運行TestDynamicLoad類的靜態方法.....
運行TestDynamicLoad類的構造方法.....
運行InnerClassA類的靜態方法.....
運行InnerClassA類的構造方法.....

Process finished with exit code 0

1.2 類加載器和雙親委派機制

1.2.1 類加載器

Java中的類加載器主要有三種:BootstrapClassLoader(引導類加載器)ExtClassLoader(擴展類加載器)AppClassLoader(應用程序類加載器)

  • BootstrapClassLoader:主要用於加載Java程序運行時必不可少的核心類,如jre/lib/rt.jarjre/lib/charsets.jar中的類。
  • ExtClassLoader:用於加載jre/lib/ext下的所有jar包中的類
  • AppClassLoader:用於加載自己編寫的類
    在這裏插入圖片描述
package com.muzili.jvm;

import com.sun.crypto.provider.DESKeyFactory;
import sun.misc.Launcher;

import java.net.URL;

public class TestJDKClassLoader {

    public static void main(String[] args) {
        /**
         * 輸出以下類的ClassLoader
         */
        System.out.println(String.class.getClassLoader());
        System.out.println(DESKeyFactory.class.getClassLoader());
        System.out.println(TestJDKClassLoader.class.getClassLoader());

        System.out.println();

        /**
         * 獲取ClassLoader
         */
        //因爲BootStrapClassLoader是通過C++實現,並實例化的故Java程序無法獲取
        ClassLoader appClassLoader=TestJDKClassLoader.class.getClassLoader();
        ClassLoader extClassLoader=appClassLoader.getParent();
        ClassLoader bootstrapClassLoader=extClassLoader.getParent();

        System.out.println("the BootStrapClassLoader:"+bootstrapClassLoader);
        System.out.println("the ExtClassLoader:"+extClassLoader);
        System.out.println("the AppClassLoader:"+appClassLoader);

        System.out.println();

        URL[] bootStrapUrls = Launcher.getBootstrapClassPath().getURLs();

        System.out.println("BootStrapLoader加載:");

        for (URL url : bootStrapUrls) {
            System.out.println(url.getPath());
        }

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

}

結果輸出:
null
sun.misc.Launcher$ExtClassLoader@4b67cf4d
sun.misc.Launcher$AppClassLoader@18b4aac2

the BootStrapClassLoader:null
the ExtClassLoader:sun.misc.Launcher$ExtClassLoader@4b67cf4d
the AppClassLoader:sun.misc.Launcher$AppClassLoader@18b4aac2

BootStrapLoader加載:
/E:/Program%20Files/Java/jdk1.8.0_191/jre/lib/resources.jar
/E:/Program%20Files/Java/jdk1.8.0_191/jre/lib/rt.jar
/E:/Program%20Files/Java/jdk1.8.0_191/jre/lib/sunrsasign.jar
/E:/Program%20Files/Java/jdk1.8.0_191/jre/lib/jsse.jar
/E:/Program%20Files/Java/jdk1.8.0_191/jre/lib/jce.jar
/E:/Program%20Files/Java/jdk1.8.0_191/jre/lib/charsets.jar
/E:/Program%20Files/Java/jdk1.8.0_191/jre/lib/jfr.jar
/E:/Program%20Files/Java/jdk1.8.0_191/jre/classes

ExtClassLoader加載:
E:\Program Files\Java\jdk1.8.0_191\jre\lib\ext;C:\WINDOWS\Sun\Java\lib\ext

AppClassLoader加載:
E:\Program Files\Java\jdk1.8.0_191\jre\lib\charsets.jar;E:\Program Files\Java\jdk1.8.0_191\jre\lib\deploy.jar;E:\Program Files\Java\jdk1.8.0_191\jre\lib\ext\access-bridge-64.jar;E:\Program Files\Java\jdk1.8.0_191\jre\lib\ext\cldrdata.jar;E:\Program Files\Java\jdk1.8.0_191\jre\lib\ext\dnsns.jar;E:\Program Files\Java\jdk1.8.0_191\jre\lib\ext\jaccess.jar;E:\Program Files\Java\jdk1.8.0_191\jre\lib\ext\jfxrt.jar;E:\Program Files\Java\jdk1.8.0_191\jre\lib\ext\localedata.jar;E:\Program Files\Java\jdk1.8.0_191\jre\lib\ext\nashorn.jar;E:\Program Files\Java\jdk1.8.0_191\jre\lib\ext\sunec.jar;E:\Program Files\Java\jdk1.8.0_191\jre\lib\ext\sunjce_provider.jar;E:\Program Files\Java\jdk1.8.0_191\jre\lib\ext\sunmscapi.jar;E:\Program Files\Java\jdk1.8.0_191\jre\lib\ext\sunpkcs11.jar;E:\Program Files\Java\jdk1.8.0_191\jre\lib\ext\zipfs.jar;E:\Program Files\Java\jdk1.8.0_191\jre\lib\javaws.jar;E:\Program Files\Java\jdk1.8.0_191\jre\lib\jce.jar;E:\Program Files\Java\jdk1.8.0_191\jre\lib\jfr.jar;E:\Program Files\Java\jdk1.8.0_191\jre\lib\jfxswt.jar;E:\Program Files\Java\jdk1.8.0_191\jre\lib\jsse.jar;E:\Program Files\Java\jdk1.8.0_191\jre\lib\management-agent.jar;E:\Program Files\Java\jdk1.8.0_191\jre\lib\plugin.jar;E:\Program Files\Java\jdk1.8.0_191\jre\lib\resources.jar;E:\Program Files\Java\jdk1.8.0_191\jre\lib\rt.jar;D:\workspace\tulingxueyuan-demo\out\production\jvm-demo-01;E:\Program Files\JetBrains\IntelliJ IDEA 2019.1.1\lib\idea_rt.jar

Process finished with exit code 0

類加載過程解析:

  1. 通過jvm創建BootstrapClassLoader實例,調用sun.misc.Launcher#getLauncher()方法獲取啓動器對象【這裏使用到了設計模式中的單例模式】
  2. Launcher類的構造方法中,創建了兩個類加載器:sun.misc.Launcher.ExtClassLoader#getExtClassLoader[擴展類加載器]、sun.misc.Launcher.AppClassLoader#getAppClassLoader[應用程序類加載器]
  3. JVM默認會使用Launcher的getClassLoader()方法返回的類加載器AppClassLoader用於加載我們的類
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package sun.misc;

import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLClassLoader;
import java.net.URLStreamHandler;
import java.net.URLStreamHandlerFactory;
import java.nio.file.Paths;
import java.security.AccessControlContext;
import java.security.AccessController;
import java.security.CodeSource;
import java.security.PermissionCollection;
import java.security.PrivilegedAction;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.security.ProtectionDomain;
import java.security.cert.Certificate;
import java.util.HashSet;
import java.util.StringTokenizer;
import java.util.Vector;
import sun.net.www.ParseUtil;

public class Launcher {
    private static URLStreamHandlerFactory factory = new Launcher.Factory();
    private static Launcher launcher = new Launcher();
    private static String bootClassPath = System.getProperty("sun.boot.class.path");
    private ClassLoader loader;
    private static URLStreamHandler fileHandler;

    public static Launcher getLauncher() {
        return launcher;
    }

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

        Thread.currentThread().setContextClassLoader(this.loader);
        String var2 = System.getProperty("java.security.manager");
        if (var2 != null) {
            SecurityManager var3 = null;
            if (!"".equals(var2) && !"default".equals(var2)) {
                try {
                    var3 = (SecurityManager)this.loader.loadClass(var2).newInstance();
                } catch (IllegalAccessException var5) {
                } catch (InstantiationException var6) {
                } catch (ClassNotFoundException var7) {
                } catch (ClassCastException var8) {
                }
            } else {
                var3 = new SecurityManager();
            }

            if (var3 == null) {
                throw new InternalError("Could not create SecurityManager: " + var2);
            }

            System.setSecurityManager(var3);
        }

    }

    public ClassLoader getClassLoader() {
        return this.loader;
    }
    
    //省略一些其他代碼
}

1.2.2 雙親委派機制

在這裏插入圖片描述

雙親委派機制,就是在加載類的時候,級別較低的類加載器首先會委託它的父級類加載器進行加載,如果父級類加載器加載不到纔會交給當前類加載器進行加載。

雙親委派機制有什麼意義:

  1. 沙箱安全:它可以保證java中的核心類不會被用戶定義的類給覆蓋,從而保證了系統的安全
  2. 防止類的重複加載:每個類加載器,都實現了緩存機制,當類第一次被加載之後就會被緩存起來,當再次加載的時候,就不用再每次都按照雙親委派機制來加載類,這樣可以提升類的加載效率

1.3 自定義類加載器

爲什麼需要自定義類加載器?

如果在實際開發中,可能有時我們想要加載一些外部目錄下的類,這個時候jvm提供的類加載器就無法滿足我們的需求了,這個時候我們需要自定義類加載器,用以加載外部目錄中的類。

1.3.1 定義一個簡單的類加載器

package com.muzili.jvm;

import java.io.FileInputStream;
import java.io.IOException;

public class MyClassLoaderTest {

    public static class MyClassLoader extends ClassLoader{

        private String clasasPath;

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

        private byte[] loadByte(String name) throws IOException {
            name = name.replace(".", "/");

            FileInputStream fis=new FileInputStream(clasasPath+"/"+name+".class");

            int len=fis.available();

            byte[] bytes=new byte[len];

            fis.read(bytes);

            fis.close();

            return bytes;
        }


        @Override
        protected Class<?> findClass(String name) throws ClassNotFoundException {

            try {
                byte[] bytes = this.loadByte(name);
                return defineClass(name,bytes,0,bytes.length);
            } catch (IOException e) {
                e.printStackTrace();
            }

            return null;
        }
    }


    public static void main(String[] args) {
        //初始化自定義的類加載器
        MyClassLoader myClassLoader=new MyClassLoader("D:\\test");

        try {
            Class<?> clazz = myClassLoader.loadClass("com.muzili.jvm.User1");

            System.out.println(clazz.getClassLoader());
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

}

運行結果:
com.muzili.jvm.MyClassLoaderTest$MyClassLoader@4554617c

Process finished with exit code 0

1.3.2 打破類加載器的雙親委派機制

package com.muzili.jvm;

import java.io.FileInputStream;
import java.io.IOException;

public class MyClassLoaderTest2 {

    public static class MyClassLoader extends ClassLoader{

        private String clasasPath;

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

        private byte[] loadByte(String name) throws IOException {
            name = name.replace(".", "/");

            FileInputStream fis=new FileInputStream(clasasPath+"/"+name+".class");

            int len=fis.available();

            byte[] bytes=new byte[len];

            fis.read(bytes);

            fis.close();

            return bytes;
        }


        @Override
        protected Class<?> findClass(String name) throws ClassNotFoundException {

            try {
                byte[] bytes = this.loadByte(name);
                return defineClass(name,bytes,0,bytes.length);
            } catch (IOException e) {
                e.printStackTrace();
            }

            return null;
        }

        /**
         * 打破雙親委派機制
         * @param name
         * @return
         * @throws ClassNotFoundException
         */
        @Override
        public 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) {
                    long t0 = System.nanoTime();

                    //直接將雙親委派機制取消不做任何處理會導致java運行時非常重要的類無法加載
                    if (!name.startsWith("com.muzili.jvm")){
                        c=this.getParent().loadClass(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.getParentDelegationTime().addTime(t1 - t0);
                        sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                        sun.misc.PerfCounter.getFindClasses().increment();
                    }
                }
                if (resolve) {
                    resolveClass(c);
                }
                return c;
            }
        }
    }


    public static void main(String[] args) {
        //初始化自定義的類加載器
        MyClassLoader myClassLoader=new MyClassLoader("D:\\test");

        try {
            Class<?> clazz = myClassLoader.loadClass("com.muzili.jvm.User1");

            System.out.println(clazz);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

}

結果輸出:
class com.muzili.jvm.User1
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章