1. 程式人生 > 其它 >2022-2023學年英語週報七年級第31期答案及試題

2022-2023學年英語週報七年級第31期答案及試題

1. 類載入執行全過程

當我們用java命令執行某個類的main函式啟動程式時,首先需要通過類載入器把主類載入到 JVM。

Window系統下:

  1. java.exe呼叫底層的jvm.dll檔案建立java虛擬機器(C++)
  2. 建立一個引導類載入器例項(C++)
  3. C++呼叫java程式碼建立JVM啟動器例項sun.misc.Launcher,該類由引導類載入器負責載入,建立其他類載入器
  4. 獲取執行類自己的載入器ClassLoader,一般是AppClassLoader
  5. 呼叫loadClass載入要執行的類
  6. 載入完成之後JVM執行執行類的main方法入口

loadClass的類載入過程:載入 >> 驗證 >> 準備 >> 解析 >> 初始化 >>

使用 >> 解除安裝

  • 載入:在硬碟上查詢並通過IO讀入位元組碼檔案,使用到類時才會載入
    • 呼叫類的 main()方法,new物件等等,在載入階段會在記憶體中生成一個代表這個類的java.lang.Class物件作為方法區這個類的各種資料的訪問入口
  • 驗證:校驗位元組碼檔案的正確性
  • 準備:給類的靜態變數分配記憶體,並賦予預設值
  • 解析:將符號引用替換為直接引用
    • 靜態連結(類載入期間完成):把一些靜態方法(符號引用,比如 main()方法)替換為指向資料所存記憶體的指標或控制代碼等(直接引用)】
    • 動態連結:程式執行期間完成的將符號引用替換為直接引用
  • 初始化:對類的靜態變數初始化為指定的值,執行靜態程式碼塊

類被載入到方法區中後主要包含 執行時常量池、型別資訊、欄位資訊、方法資訊、類載入器的引用、對應class例項的引用等資訊

類載入器的引用:這個類到類載入器例項的引用

對應class例項的引用:類載入器在載入類資訊放到方法區中後,會建立一個對應的Class 型別的 物件例項放到堆(Heap)中, 作為開發人員訪問方法區中類定義的入口和切入點。

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

2. 類載入器和雙親委派機制

Java裡有如下幾種類載入器:

  • 引導類載入器:負責載入支撐JVM執行的位於JRE的lib目錄下的核心類庫,比如 rt.jar、charsets.jar等
  • 擴充套件類載入器:負責載入支撐JVM執行的位於JRE的lib目錄下的ext擴充套件目錄中的JAR 類包
  • 應用程式類載入器:負責載入ClassPath路徑下的類包,主要就是載入你自己寫的那 些類
  • 自定義載入器:負責載入使用者自定義路徑下的類包
public class TestJDKClassloader {
    public static void main(String[] args) {
        System.out.println("String類的載入器:" + String.class.getClassLoader());
        System.out.println("ext目錄下的載入器:" + com.sun.crypto.provider.DESKeyFactory.class.getClassLoader());
        System.out.println("自己編寫的程式碼的載入器:" + TestJDKClassloader.class.getClassLoader());

        System.out.println("引導類載入器載入一下檔案:");
        URL[] urLs = Launcher.getBootstrapClassPath().getURLs();
        for (URL urL : urLs) {
            System.out.println(urL);
        }

        System.out.print("extClassLoader載入以下檔案:");
        System.out.println(System.getProperty("java.ext.dirs"));

        System.out.print("appClassLoader載入以下檔案:");
        System.out.println(System.getProperty("java.class.path"));
    }
}

輸出結果:

類載入器初始化過程:主要看com.misc.Launcher原始碼

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

主要存在兩個類載入器:

  1. sun.misc.Launcher.ExtClassLoader(擴充套件類載入器)
  2. sun.misc.Launcher.AppClassLoader(應用類載入器)

JVM預設使用Launcher的getClassLoader()方法返回的類載入器AppClassLoader的例項載入我們 的應用程式。

public Launcher() {
        Launcher.ExtClassLoader var1;
        try {
            //構建擴充套件類載入器,在構造的過程中將其父類載入器設定位null
            var1 = Launcher.ExtClassLoader.getExtClassLoader();
        } catch (IOException var10) {
            throw new InternalError("Could not create extension class loader", var10);
        }
        try {
            //構造應用類載入器,在構造的過程中將其父載入器設定為ExtClassLoader
            //Launcher的loader屬性值是AppClassLoader,我們一般都是用這個類載入器來載入我們自己寫的應用程式
            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);
        }
    }

雙親委派機制

載入某個類時會先委託父載入器尋找目標類,找不到再 委託上層父載入器載入,如果所有父載入器在自己的載入類路徑下都找不到目標類,則在自己的 類載入路徑中查詢並載入目標類。說簡單點就是,先找父親載入,不行再由兒子自己載入

應用程式類載入器AppClassLoader載入類的雙親委派機制原始碼,AppClassLoader 的loadClass方法最終會呼叫其父類ClassLoader的loadClass方法,該方法的大體邏輯如下:

  1. 首先,檢查一下指定名稱的類是否已經載入過,如果載入過了,就不需要再載入,直接 返回
  2. 如果此類沒有載入過,那麼,再判斷一下是否有父載入器;如果有父載入器,則由父加 載器載入(即呼叫parent.loadClass(name, false);).或者是呼叫bootstrap類載入器來加 載
  3. 如果父載入器及bootstrap類載入器都沒有找到指定的類,那麼呼叫當前類載入器的findClass方法來完成類載入

ClassLoader.java

protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
{
    synchronized (getClassLoadingLock(name)) {
        //檢查當前類載入器是否已經載入了該類
        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) {
                long t1 = System.nanoTime();
                //都會呼叫URLClassLoader的findClass方法在載入器的類路徑裡查詢並載入該類
                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;
    }
}

雙親委派機制設計的目的

  • 沙箱安全機制:自己寫的java.lang.String.class類不會被載入,這樣便可以防止核心 API庫被隨意篡改
  • 避免類的重複載入:當父親已經載入了該類時,就沒有必要子ClassLoader再載入一 次,保證被載入類的唯一性

全盤負責委託機制 :指當一個ClassLoder裝載一個類時,除非顯示的使用另外一個ClassLoder,該類所依賴及引用的類也由這個ClassLoder載入

3. 自定義載入器

自定義類載入器只需要繼承 java.lang.ClassLoader 類,該類有兩個核心方法,一個是 loadClass(String, boolean),實現了雙親委派機制,還有一個方法是findClass,預設實現是空 方法,所以我們自定義類載入器主要是重寫findClass方法

public class MyClassLoaderTest1 {
    public static void main(String[] args) throws Exception {
        MyClassLoader1 classLoader = new MyClassLoader1("E:\\github\\java-interview\\java-learn\\target\\classes");
        Class clazz = classLoader.loadClass("com.hsm.java.jvm.MyClassLoaderTest1");
        Object obj = clazz.newInstance();
        System.out.println(clazz.getClassLoader().getClass().getName());
    }


    static class MyClassLoader1 extends ClassLoader {
        private String classPath;

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

        @Override
        protected Class<?> findClass(String name) throws ClassNotFoundException {
            try {
                byte[] data = loadByte(name);
                //defineClass將一個位元組陣列轉為Class物件,這個位元組陣列是class檔案讀取後最終的位元組 陣列。
                return defineClass(name, data, 0, data.length);
            } catch (Exception e) {
                throw new ClassNotFoundException();
            }

        }

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

4. 打破雙親委託

public class MyClassLoaderTest2 {
    public static void main(String[] args) throws Exception {
        MyClassLoader classLoader = new MyClassLoader("E:\\github\\java-interview\\java-learn\\target\\classes");
        Class clazz = classLoader.loadClass("com.hsm.java.jvm.MyClassLoaderTest2");
        Object obj = clazz.newInstance();
        System.out.println(clazz.getClassLoader().getClass().getName());
    }


    static class MyClassLoader extends ClassLoader {
        private String classPath;

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

        @Override
        public Class<?> findClass(String name) throws ClassNotFoundException {
            try {
                byte[] data = loadByte(name);
                //defineClass將一個位元組陣列轉為Class物件,這個位元組陣列是class檔案讀取後最終的位元組 陣列。
                return defineClass(name, data, 0, data.length);
            } catch (Exception e) {
                throw new ClassNotFoundException();
            }

        }

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

        protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
            synchronized (getClassLoadingLock(name)) {
                Class<?> c = findLoadedClass(name);
                if (c == null) {
                    long t1 = System.nanoTime();

                    if(!name.startsWith("com.hsm")){
                        //非自定義的類還是走雙親委派載入
                        c = this.getParent().loadClass(name);
                    }else{
                        c = findClass(name);
                    }
                    sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    sun.misc.PerfCounter.getFindClasses().increment();

                }
                if (resolve) {
                    resolveClass(c);
                }
                return c;
            }
        }
    }
}