天天看點

(2)JVM 類加載之類加載器初始化前言一、JVM的四種類加載器二、類加載器的初始化總結

目錄

  • 前言
  • 一、JVM的四種類加載器
    • 1、引導類加載器
    • 2、擴充類加載器
    • 3、應用程式類加載器
    • 4、自定義類加載器
  • 二、類加載器的初始化
  • 總結

前言

上一節(1) 類加載之流程了解記錄了類加載過程中五個具體步驟的流程了解。那麼對于一個需要加載的類,到底是什麼東西來對它進行加載呢?

其實在程式啟動之初,在加載包括核心類庫在内的所有類庫之前,JVM會先建立一個用C++實作的引導類加載器執行個體,然後用這個類加載器加載JVM啟動器類"sum.misc.Launcher"并跨語言調用JAVA代碼來建立執行個體,通過該執行個體調用"sum.misc.Launcher.getLauncher()"方法來初始化擴充類加載器和應用程式類加載器。上一節已經貼過流程圖,在這裡再次放上:

(2)JVM 類加載之類加載器初始化前言一、JVM的四種類加載器二、類加載器的初始化總結

一、JVM的四種類加載器

1、引導類加載器

運作以下代碼:

package com.jim.jvm.classload;

import sun.misc.Launcher;
import java.net.URL;
import java.util.List;

public class ClassLoaderTest {
    public static void main(String[] args) {
        System.out.println("加載List類的類加載器:" + List.class.getClassLoader());
        System.out.println();
        System.out.println("引導類加載器 加載以下檔案:");
        URL[] urls = Launcher.getBootstrapClassPath().getURLs();
        for (URL url : urls) {
            System.out.println(url);
        }
    }
}
           

得到結果如下:

加載List類的類加載器:null

引導類加載器 加載以下檔案:
file:/E:/Program%20Files%20(x86)/java/jdk1.8.0_131/jre/lib/resources.jar
file:/E:/Program%20Files%20(x86)/java/jdk1.8.0_131/jre/lib/rt.jar
file:/E:/Program%20Files%20(x86)/java/jdk1.8.0_131/jre/lib/sunrsasign.jar
file:/E:/Program%20Files%20(x86)/java/jdk1.8.0_131/jre/lib/jsse.jar
file:/E:/Program%20Files%20(x86)/java/jdk1.8.0_131/jre/lib/jce.jar
file:/E:/Program%20Files%20(x86)/java/jdk1.8.0_131/jre/lib/charsets.jar
file:/E:/Program%20Files%20(x86)/java/jdk1.8.0_131/jre/lib/jfr.jar
file:/E:/Program%20Files%20(x86)/java/jdk1.8.0_131/jre/classes
           

有以下問題:

  1. 為什麼加載List類的類加載器是null呢?

    答:其實在前言已經說明了,引導類加載器執行個體是C++對象,不是Java對象,使用Java的方法當然行不通啊。

  2. 引導類加載器能夠加載哪些檔案?

    答:看輸出的結果能發現,引導類加載器加載的是jdk.jre.lib目錄下的類。

2、擴充類加載器

運作以下代碼:

package com.jim.jvm.classload;

public class ClassLoaderTest {
    public static void main(String[] args) {
        System.out.println("加載RSAKeyPairGenerator類的類加載器:" + sun.security.mscapi.RSAKeyPairGenerator.class.getClassLoader());
		System.out.println();
        System.out.println("擴充類加載器 加載以下路徑的檔案:");
        System.out.println(System.getProperty("java.ext.dirs"));
    }
}
           

得到結果如下:

加載RSAKeyPairGenerator類的類加載器:sun.misc.Launcher$ExtClassLoader@29453f44

擴充類加載器 加載以下路徑的檔案:
E:\Program Files (x86)\java\jdk1.8.0_131\jre\lib\ext;
C:\WINDOWS\Sun\Java\lib\ext
           

可以發現,擴充類加載器加載的是jdk.jre.lib.ext目錄下的類。

3、應用程式類加載器

運作以下代碼:

package com.jim.jvm.classload;

public class ClassLoaderTest {
    public static void main(String[] args) {
        System.out.println("加載自定義ClassLoaderTest類的類加載器:" + com.jim.jvm.classload.ClassLoaderTest.class.getClassLoader());
        System.out.println();
        System.out.println("應用程式類加載器 加載以下(路徑的)檔案:");
        System.out.println(System.getProperty("java.class.path"));
    }
}
           

得到結果如下:

加載自定義ClassLoaderTest類的類加載器:sun.misc.Launcher$AppClassLoader@18b4aac2

應用程式類加載器 加載以下(路徑的)檔案:
E:\Program Files (x86)\java\jdk1.8.0_131\jre\lib\charsets.jar;
E:\Program Files (x86)\java\jdk1.8.0_131\jre\lib\deploy.jar;
E:\Program Files (x86)\java\jdk1.8.0_131\jre\lib\ext\access-bridge-64.jar;
E:\Program Files (x86)\java\jdk1.8.0_131\jre\lib\ext\cldrdata.jar;
E:\Program Files (x86)\java\jdk1.8.0_131\jre\lib\ext\dnsns.jar;
E:\Program Files (x86)\java\jdk1.8.0_131\jre\lib\ext\jaccess.jar;
E:\Program Files (x86)\java\jdk1.8.0_131\jre\lib\ext\jfxrt.jar;
E:\Program Files (x86)\java\jdk1.8.0_131\jre\lib\ext\localedata.jar;
E:\Program Files (x86)\java\jdk1.8.0_131\jre\lib\ext\nashorn.jar;
E:\Program Files (x86)\java\jdk1.8.0_131\jre\lib\ext\sunec.jar;
E:\Program Files (x86)\java\jdk1.8.0_131\jre\lib\ext\sunjce_provider.jar;
E:\Program Files (x86)\java\jdk1.8.0_131\jre\lib\ext\sunmscapi.jar;
E:\Program Files (x86)\java\jdk1.8.0_131\jre\lib\ext\sunpkcs11.jar;
E:\Program Files (x86)\java\jdk1.8.0_131\jre\lib\ext\zipfs.jar;
E:\Program Files (x86)\java\jdk1.8.0_131\jre\lib\javaws.jar;
E:\Program Files (x86)\java\jdk1.8.0_131\jre\lib\jce.jar;
E:\Program Files (x86)\java\jdk1.8.0_131\jre\lib\jfr.jar;
E:\Program Files (x86)\java\jdk1.8.0_131\jre\lib\jfxswt.jar;
E:\Program Files (x86)\java\jdk1.8.0_131\jre\lib\jsse.jar;
E:\Program Files (x86)\java\jdk1.8.0_131\jre\lib\management-agent.jar;
E:\Program Files (x86)\java\jdk1.8.0_131\jre\lib\plugin.jar;
E:\Program Files (x86)\java\jdk1.8.0_131\jre\lib\resources.jar;
E:\Program Files (x86)\java\jdk1.8.0_131\jre\lib\rt.jar;
D:\idea_projects_2019.2.4\jvm\out\production\jvm;
E:\Program Files\JetBrains\IntelliJ IDEA 2019.2.4\lib\idea_rt.jar
           

可以發現,我們在寫程式時,自定義的那些類一般是由應用程式類加載器來加載的。

有個問題:

  1. 仔細觀察會發現,應用程式類加載器貌似把上面兩種類加載器能加載的檔案全部都加載了?那JVM設計者為什麼還要分這麼幾種類加載器呢?

    答:請看我的第三篇文章(3)JVM 類加載之雙親委派機制。

4、自定義類加載器

請看我的第三篇文章(3)JVM 類加載之雙親委派機制。

二、類加載器的初始化

前言中說到JVM會在加載類之前先初始化引導類加載器、擴充類加載器和應用程式類加載器,那麼是怎樣初始化的呢?引導類加載器是用C++寫的,我暫時不知道,不過後面兩種可以通過分析Java源碼來了解。先在下圖中标名初始化類加載器的時機,可結合本文第一張圖看。

(2)JVM 類加載之類加載器初始化前言一、JVM的四種類加載器二、類加載器的初始化總結

到底是怎麼初始化擴充類加載器和應用程式類加載器的呢?從上圖中可以看到,這是在調用執行"getLauncher()"函數的過程中進行的。

那麼我們找到rt,jar包中的"sum.misc.Launcher"類:

(2)JVM 類加載之類加載器初始化前言一、JVM的四種類加載器二、類加載器的初始化總結

找到"getLauncher()"函數:

public static Launcher getLauncher() {
    return launcher;
}
           

傳回了一個launcher,這個launcher是:

發現是一個靜态變量,那麼上圖中的"sum.misc.Launcher.getLauncher()"就解釋得通了:引導類加載器在調用Java代碼建立Launcher類執行個體後,執行個體調用靜态方法傳回了一個在類加載過程中就已經初始化好的靜态變量,那麼這個靜态變量是什麼呢?接下來進入構造方法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);
    }
    ...
}
           

會發現,在構造方法中建立了一個Launcher.ExtClassLoader類型的變量var1,也就是擴充類加載器,進入代碼:

中的ExtClassLoader,發現是個靜态内部類:

static class ExtClassLoader extends URLClassLoader {
    public static Launcher.ExtClassLoader getExtClassLoader() throws IOException {
        final File[] var0 = getExtDirs();

        try {
            return (Launcher.ExtClassLoader)AccessController.doPrivileged(new PrivilegedExceptionAction<Launcher.ExtClassLoader>() {
                public Launcher.ExtClassLoader run() throws IOException {
                    int var1 = var0.length;

                    for(int var2 = 0; var2 < var1; ++var2) {
                        MetaIndex.registerDirectory(var0[var2]);
                    }

                    return new Launcher.ExtClassLoader(var0);
                }
            });
        } catch (PrivilegedActionException var2) {
            throw (IOException)var2.getException();
        }
    }
	...
    public ExtClassLoader(File[] var1) throws IOException {
        super(getExtURLs(var1), (ClassLoader)null, Launcher.factory);
        SharedSecrets.getJavaNetAccess().getURLClassPath(this).initLookupCache(this);
    }
	...
}
           

其中的 “getExtClassLoader()” 是一個靜态函數,傳回值是:

跟進ExtClassLoader(var0),發現是ExtClassLoader的構造函數:

public ExtClassLoader(File[] var1) throws IOException {
    super(getExtURLs(var1), (ClassLoader)null, Launcher.factory);
    SharedSecrets.getJavaNetAccess().getURLClassPath(this).initLookupCache(this);
}
           

也就是說,getExtClassLoader()傳回的是一個擴充類構造器執行個體!那麼:

中的var1是一個擴充類構造器執行個體!

類似的,應用程式類構造器的初始化也大同小異:

public Launcher() {
    ...
    try {
        this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
    } catch (IOException var9) {
        throw new InternalError("Could not create application class loader", var9);
    }
    ...
}
           
static class AppClassLoader extends URLClassLoader {
    final URLClassPath ucp = SharedSecrets.getJavaNetAccess().getURLClassPath(this);

    public static ClassLoader getAppClassLoader(final ClassLoader var0) throws IOException {
        final String var1 = System.getProperty("java.class.path");
        final File[] var2 = var1 == null ? new File[0] : Launcher.getClassPath(var1);
        return (ClassLoader)AccessController.doPrivileged(new PrivilegedAction<Launcher.AppClassLoader>() {
            public Launcher.AppClassLoader run() {
                URL[] var1x = var1 == null ? new URL[0] : Launcher.pathToURLs(var2);
                return new Launcher.AppClassLoader(var1x, var0);
            }
        });
    }

    AppClassLoader(URL[] var1, ClassLoader var2) {
        super(var1, var2, Launcher.factory);
        this.ucp.initLookupCache(this);
    }
	...
}
           
AppClassLoader(URL[] var1, ClassLoader var2) {
    super(var1, var2, Launcher.factory);
    this.ucp.initLookupCache(this);
}
           

綜上,在執行這行代碼後:

擴充類加載器和應用程式類加載器就已經被初始化了。

總結

看源碼有點艱難,不過看懂後感覺,至少從邏輯上類加載器的初始化并不是很難。