《JVM 類加載器》

Java虛擬機設計團隊有意把類加載階段中的"通過一個類的全限定名來獲取描述該類的二進制字節流"這個動作放到Java虛擬機外部去實現,以便讓應用程序自己決定如何獲取所需的類。實現這個動作的代碼被稱爲"類加載器"(ClassLoader)

類加載器雖然只用於實現類的加載動作,但它在Java程序中起到的作用卻遠超類加載階段。對於任意一個類,都必須由加載它的類加載器和這個類本身一起共同確立其在Java虛擬機中的唯一性。每一個類加載器,都擁有一個獨立的類名稱空間。比較兩個類是否相等,只有在這兩個類是由同一個類加載器加載的前提下才有意義,否則即使這兩個類來源於同一個Class文件,被同一個Java虛擬機加載,只要加載它們的類加載器不同,那這兩個類就必定不相等。

顧名思義,類的加載器就是負責類的加載職責,對於任意一個class,都需要由加載它的類加載器和這個類本身確立其在JVM中的唯一性,這也就是運行時包。任何一個對象的class在JVM中只會存在唯一的一份,如String.class、Object.class在堆內存以及方法區中肯定是唯一的,但是不能絕對理解爲我們自定義的類在JVM中同樣也是。

JVM爲我們提供了三大內置的類加載器,不同的類加載器負責將不同類加載到JVM內存之中,並且它們之間嚴格遵守父委託機制。

雙親委派機制的工作過程:如果一個類加載器收到類加載的請求,它首先不會自己嘗試加載這個類,而是把這個請求委派爲自己父類加載器去完成,每一個層次的類加載器都是如此,因此所有的加載請求最終都應該傳送到最頂層的啓動類加載器中,只有當父加載器反饋自己無法完成這個加載請求(它的搜索範圍中沒有找到所需的類)時,子加載器纔會嘗試自己去完成加載。

啓動類加載器(BootStrap)

啓動類加載器又稱爲BootStrap類加載器,該類加載器是最爲頂層的加載器,其沒有任何父類加載器,它由C++編寫,這個類加載器負責虛擬機核心類庫的加載,比如整個java.lang包都是有啓動類加載器所加載的,存放在<JAVA_HOME>\lib目錄或是被-Xbootclasspath參數所指定的路徑中存放的,而且是Java虛擬機能識別的(按照文件名識別,如rt.jar、tools.jar,名字不符合的類庫即使放在lib目錄下也不會被加載)類庫加載到虛擬機內存中。

public class BootStrapClassLoader {

    public static void main(String[] args) {
        
        System.out.println("BootStrap:" + String.class.getClassLoader());
        System.out.println(System.getProperty("sun.boot.class.path"));
    }
}

輸出:
BootStrap:null
/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/jre/lib/resources.jar:
/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/jre/lib/rt.jar:
/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/jre/lib/sunrsasign.jar:
/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/jre/lib/jsse.jar:
/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/jre/lib/jce.jar:
/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/jre/lib/charsets.jar:
/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/jre/lib/jfr.jar:
/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/jre/classes

上述程序輸出結果顯示,其中String.class的類加載器是啓動類加載器,啓動類加載器是獲取不到引用的,因此輸出爲null,而啓動類加載器所在的加載路徑可以通過sun.boot.class.path這個系統屬性來獲得。

擴展類加載器

擴展類加載器的父加載器是根加載器,擴展類加載器是由純Java語言編寫,它是java.lang.URLClassLoader的子類,這個類加載器是在類sun.misc.Launcher$ExtClassLoader中以Java代碼的形式實現的。它負責加載<JAVA_HOME>\lib\ext目錄中,或者被java.ext.dirs系統變量所指定的路徑中所有的類庫。

由於擴展類加載器是由Java代碼實現的,開發者可以直接在程序中使用擴展類加載器來加載Class文件。

public class ExtClassLoader {

    public static void main(String[] args) {
        System.out.println(System.getProperty("java.ext.dirs"));
    }
}

運行上邊的程序會得到擴展類加載器加載資源的路徑,輸出:
/Users/lihuan/Library/Java/Extensions:/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/jre/lib/ext:
/Library/Java/Extensions:/Network/Library/Java/Extensions:/System/Library/Java/Extensions:/usr/lib/java

系統類加載器

系統類加載器是一種常見的類加載器,它負責加載用戶路徑(ClassPath)上所有的類庫,開發者同樣可以直接在代碼中使用這個類加載器。系統類加載器的加載路徑一般通過-classpath或者-cp指定,同樣也可以通過系統屬性java.class.path進行獲取。

public class ApplicationClassLoader {

    public static void main(String[] args) {
        System.out.println(System.getProperty("java.class.path"));
        System.out.println(ApplicationClassLoader.class.getClassLoader());
    }
}

輸出:
/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/jre/lib/charsets.jar:
/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/jre/lib/deploy.jar:
/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/jre/lib/ext/cldrdata.jar:
/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/jre/lib/ext/dnsns.jar:
/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/jre/lib/ext/jaccess.jar:
/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/jre/lib/ext/jfxrt.jar:
/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/jre/lib/ext/localedata.jar:
/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/jre/lib/ext/nashorn.jar:
/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/jre/lib/ext/sunec.jar:
/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/jre/lib/ext/sunjce_provider.jar:
/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/jre/lib/ext/sunpkcs11.jar:
/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/jre/lib/ext/zipfs.jar:
/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/jre/lib/javaws.jar:
/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/jre/lib/jce.jar:
/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/jre/lib/jfr.jar:
/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/jre/lib/jfxswt.jar:
/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/jre/lib/jsse.jar:
/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/jre/lib/management-agent.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/jre/lib/plugin.jar:
/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/jre/lib/resources.jar:
/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/jre/lib/rt.jar:
/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/lib/ant-javafx.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/lib/dt.jar:
/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/lib/javafx-mx.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/lib/jconsole.jar:
/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/lib/packager.jar:
/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/lib/sa-jdi.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/lib/tools.jar:
/Users/lihuan/Documents/projects/git/me/spring-master/target/classes:/Users/lihuan/Documents/opt/maven/repository/org/springframework/boot/spring-boot-starter/2.3.2.RELEASE/spring-boot-starter-2.3.2.RELEASE.jar:
/Users/lihuan/Documents/opt/maven/repository/org/springframework/boot/spring-boot/2.3.2.RELEASE/spring-boot-2.3.2.RELEASE.jar:
/Users/lihuan/Documents/opt/maven/repository/org/springframework/boot/spring-boot-autoconfigure/2.3.2.RELEASE/spring-boot-autoconfigure-2.3.2.RELEASE.jar:
/Users/lihuan/Documents/opt/maven/repository/org/springframework/boot/spring-boot-starter-logging/2.3.2.RELEASE/spring-boot-starter-logging-2.3.2.RELEASE.jar:
/Users/lihuan/Documents/opt/maven/repository/ch/qos/logback/logback-classic/1.2.3/logback-classic-1.2.3.jar:
/Users/lihuan/Documents/opt/maven/repository/ch/qos/logback/logback-core/1.2.3/logback-core-1.2.3.jar:
/Users/lihuan/Documents/opt/maven/repository/org/apache/logging/log4j/log4j-to-slf4j/2.13.3/log4j-to-slf4j-2.13.3.jar:
/Users/lihuan/Documents/opt/maven/repository/org/apache/logging/log4j/log4j-api/2.13.3/log4j-api-2.13.3.jar:
/Users/lihuan/Documents/opt/maven/repository/org/slf4j/jul-to-slf4j/1.7.30/jul-to-slf4j-1.7.30.jar:/Users/lihuan/Documents/opt/maven/repository/jakarta/annotation/jakarta.annotation-api/1.3.5/jakarta.annotation-api-1.3.5.jar:/Users/lihuan/Documents/opt/maven/repository/org/springframework/spring-core/5.2.8.RELEASE/spring-core-5.2.8.RELEASE.jar:/Users/lihuan/Documents/opt/maven/repository/org/springframework/spring-jcl/5.2.8.RELEASE/spring-jcl-5.2.8.RELEASE.jar:
/Users/lihuan/Documents/opt/maven/repository/org/yaml/snakeyaml/1.26/snakeyaml-1.26.jar:
/Users/lihuan/Documents/opt/maven/repository/org/apache/commons/commons-lang3/3.9/commons-lang3-3.9.jar:/Users/lihuan/Documents/opt/maven/repository/org/apache/lucene/lucene-suggest/5.5.2/lucene-suggest-5.5.2.jar:/Users/lihuan/Documents/opt/maven/repository/org/apache/lucene/lucene-analyzers-common/5.5.2/lucene-analyzers-common-5.5.2.jar:
/Users/lihuan/Documents/opt/maven/repository/org/apache/lucene/lucene-core/5.5.2/lucene-core-5.5.2.jar:/Users/lihuan/Documents/opt/maven/repository/org/apache/lucene/lucene-misc/5.5.2/lucene-misc-5.5.2.jar:
/Users/lihuan/Documents/opt/maven/repository/org/apache/lucene/lucene-queries/5.5.2/lucene-queries-5.5.2.jar:
/Users/lihuan/Documents/opt/maven/repository/org/apache/lucene/lucene-grouping/5.5.2/lucene-grouping-5.5.2.jar:
/Users/lihuan/Documents/opt/maven/repository/org/apache/lucene/lucene-queryparser/7.2.1/lucene-queryparser-7.2.1.jar:
/Users/lihuan/Documents/opt/maven/repository/org/apache/lucene/lucene-sandbox/7.2.1/lucene-sandbox-7.2.1.jar:
/Users/lihuan/Documents/opt/maven/repository/com/google/protobuf/protobuf-java/3.11.4/protobuf-java-3.11.4.jar:
/Users/lihuan/Documents/opt/maven/repository/com/googlecode/protobuf-java-format/protobuf-java-format/1.4/protobuf-java-format-1.4.jar:
/Users/lihuan/Documents/opt/maven/repository/org/ansj/ansj_seg/5.1.6/ansj_seg-5.1.6.jar:
/Users/lihuan/Documents/opt/maven/repository/org/nlpcn/nlp-lang/1.7.7/nlp-lang-1.7.7.jar:
/Users/lihuan/Documents/opt/maven/repository/com/google/guava/guava/27.1-jre/guava-27.1-jre.jar:
/Users/lihuan/Documents/opt/maven/repository/com/google/guava/failureaccess/1.0.1/failureaccess-1.0.1.jar:/Users/lihuan/Documents/opt/maven/repository/com/google/guava/listenablefuture/9999.0-empty-to-avoid-conflict-with-guava/listenablefuture-9999.0-empty-to-avoid-conflict-with-guava.jar:
/Users/lihuan/Documents/opt/maven/repository/com/google/code/findbugs/jsr305/3.0.2/jsr305-3.0.2.jar:
/Users/lihuan/Documents/opt/maven/repository/org/checkerframework/checker-qual/2.5.2/checker-qual-2.5.2.jar:
/Users/lihuan/Documents/opt/maven/repository/com/google/errorprone/error_prone_annotations/2.2.0/error_prone_annotations-2.2.0.jar:
/Users/lihuan/Documents/opt/maven/repository/com/google/j2objc/j2objc-annotations/1.1/j2objc-annotations-1.1.jar:
/Users/lihuan/Documents/opt/maven/repository/org/codehaus/mojo/animal-sniffer-annotations/1.17/animal-sniffer-annotations-1.17.jar:
/Users/lihuan/Documents/opt/maven/repository/org/springframework/boot/spring-boot-starter-thymeleaf/2.3.2.RELEASE/spring-boot-starter-thymeleaf-2.3.2.RELEASE.jar:/Users/lihuan/Documents/opt/maven/repository/org/thymeleaf/thymeleaf-spring5/3.0.11.RELEASE/thymeleaf-spring5-3.0.11.RELEASE.jar:
/Users/lihuan/Documents/opt/maven/repository/org/thymeleaf/thymeleaf/3.0.11.RELEASE/thymeleaf-3.0.11.RELEASE.jar:
/Users/lihuan/Documents/opt/maven/repository/org/attoparser/attoparser/2.0.5.RELEASE/attoparser-2.0.5.RELEASE.jar:
/Users/lihuan/Documents/opt/maven/repository/org/unbescape/unbescape/1.1.6.RELEASE/unbescape-1.1.6.RELEASE.jar:
/Users/lihuan/Documents/opt/maven/repository/org/slf4j/slf4j-api/1.7.30/slf4j-api-1.7.30.jar:/Users/lihuan/Documents/opt/maven/repository/org/thymeleaf/extras/thymeleaf-extras-java8time/3.0.4.RELEASE/thymeleaf-extras-java8time-3.0.4.RELEASE.jar:/Users/lihuan/Documents/opt/maven/repository/org/springframework/boot/spring-boot-starter-aop/2.3.2.RELEASE/spring-boot-starter-aop-2.3.2.RELEASE.jar:
/Users/lihuan/Documents/opt/maven/repository/org/springframework/spring-aop/5.2.8.RELEASE/spring-aop-5.2.8.RELEASE.jar:
/Users/lihuan/Documents/opt/maven/repository/org/aspectj/aspectjweaver/1.9.6/aspectjweaver-1.9.6.jar:
/Users/lihuan/Documents/opt/maven/repository/org/springframework/spring-context-support/4.3.13.RELEASE/spring-context-support-4.3.13.RELEASE.jar:
/Users/lihuan/Documents/opt/maven/repository/org/springframework/spring-beans/5.2.8.RELEASE/spring-beans-5.2.8.RELEASE.jar:
/Users/lihuan/Documents/opt/maven/repository/org/springframework/spring-context/5.2.8.RELEASE/spring-context-5.2.8.RELEASE.jar:
/Users/lihuan/Documents/opt/maven/repository/org/springframework/spring-expression/5.2.8.RELEASE/spring-expression-5.2.8.RELEASE.jar:
/Users/lihuan/Documents/opt/maven/repository/org/springframework/boot/spring-boot-starter-cache/2.3.2.RELEASE/spring-boot-starter-cache-2.3.2.RELEASE.jar:
/Users/lihuan/Documents/opt/maven/repository/com/github/ben-manes/caffeine/caffeine/2.5.5/caffeine-2.5.5.jar:/Users/lihuan/Documents/opt/maven/repository/com/github/rholder/guava-retrying/2.0.0/guava-retrying-2.0.0.jar:
/Users/lihuan/Documents/opt/maven/repository/org/apache/httpcomponents/httpclient/4.5.10/httpclient-4.5.10.jar:
/Users/lihuan/Documents/opt/maven/repository/org/apache/httpcomponents/httpcore/4.4.13/httpcore-4.4.13.jar:
sun.misc.Launcher$AppClassLoader@18b4aac2

自定義類加載器

自定義類加載器都是ClassLoader的直接子類或者間接子類,java.lang.ClassLoader是一個抽象類,它裏邊並沒有抽象方法,但是有一個findClass方法,務必實現該方法,否則會拋出Class找不到的異常。

package com.spring.master.java.loader;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

/**
 * 自定義類加載器必須是ClassLoader的子類或者直接子類
 */
public class MyClassLoader extends ClassLoader{

    /**
     * 定義默認的class存放路徑
     */
    private final static Path  DEFAULT_CLASS_DIR = Paths.get("/Users/lihuan/Downloads/classloader1");

    private final Path classDir;

    /**
     * 使用默認的class路徑
     */
    public MyClassLoader() {
        super();
        this.classDir = DEFAULT_CLASS_DIR;
    }

    /**
     * 允許傳入指定路徑的class路徑
     * @param classDir
     */
    public MyClassLoader(String classDir) {
        super();
        this.classDir = Paths.get(classDir);
    }

    /**
     * 指定class路徑,同時指定父類加載器
     * @param classDir
     * @param parent
     */
    public MyClassLoader(String classDir, ClassLoader parent) {
        super(parent);
        this.classDir = Paths.get(classDir);
    }

    /**
     * 重寫父類的findClass方法,這是至關重要的一步
     * @param name
     * @return
     * @throws ClassNotFoundException
     */
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {

        // 讀取class的二進制數據
        byte[] classBytes = this.readClassBytes(name);
        // 如果數據爲null或者沒有讀到任何信息,則拋出ClassNotFoundException
        if (null == classBytes || classBytes.length == 0) {
            throw new ClassNotFoundException("Can not load the class" + name);
        }
        // 調用defineClass方法定義class
        return this.defineClass(name, classBytes, 0, classBytes.length);
    }

    /**
     * 將class文件讀入內存
     * @param name
     * @return
     */
    private byte[] readClassBytes(String name) throws ClassNotFoundException{

        String classPath = name.replace(".", "/");
        Path classFullPath = classDir.resolve(Paths.get(classPath + ".class"));

        if (!classFullPath.toFile().exists()) {
            throw new ClassNotFoundException("The class" + name + "not found");
        }

        try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
            Files.copy(classFullPath, baos);
            return baos.toByteArray();
        } catch (IOException e) {
            throw new ClassNotFoundException("load the class" + name + "occur error", e);
        }
    }

    @Override
    public String toString() {
        return "My ClassLoader";
    }
}

全路徑格式:

  • java.lang.String:包名.類名
  • javax.swing.JSpinner$DefaultEditor:包名.類名$內部類
  • java.security.KeyStore$Builder$FileBuilder$1:包名.類名$內部類$內部類$匿名內部類
  • java.net.URLClassLoader$3$1:包名.類名$匿名內部類$匿名內部類

需要注意的是defineClass方法,該方法的完整方法描述是defineClass(String name, byte[] b, int off, int len),其中,第一個是要定義類的名字,一般與findClass方法中的類名保持一致即可;第二個參數是class文件的二進制字節數組;第三個參數是字節數組的偏移量;第四個參數從偏移量開始讀取多長的byte數據。

雙親委託模型要求除了頂層的啓動類加載器外,其餘的類加載器都應該有自己的父類加載器

1. 測試類加載器

測試類:

public class HelloWord {

    static {
        System.out.println("Hello World Class is Init");
    }

    public String welcome() {
        return "Hello World";
    }
}
public class MyClassLoaderTest {

    public static void main(String[] args)
            throws ClassNotFoundException,
            IllegalAccessException,
            InstantiationException,
            NoSuchMethodException,
            InvocationTargetException {

        String path = "com.spring.master.java.loader.HelloWord";

        // 聲明一個MyClassLoader
        MyClassLoader classLoader = new MyClassLoader();

        Class<?> aClass = classLoader.loadClass(path);

        System.out.println(aClass.getClassLoader()); #註釋1

        Object object = aClass.newInstance();
        System.out.println(object);

        Method method = aClass.getMethod("welcome");
        System.out.println(method);

        String result = (String)method.invoke(object);
        System.out.println(result);
    }

}

輸出:
sun.misc.Launcher$AppClassLoader@18b4aac2
Hello World Class is Init
com.spring.master.java.loader.HelloWord@3d494fbf
public java.lang.String com.spring.master.java.loader.HelloWord.welcome()
Hello World

在測試代碼註釋掉"註釋1"以下的代碼你會發現,雖然aClass被成功加載並且輸出了類加載器的信息,但是HelloWorld的靜態代碼塊並未得到輸出,那是因爲使用類加載器loadClass並不會導致類的主動初始化,它只是執行了加載過程中的加載階段而已。

雙親委派機制

站在Java虛擬機的角度來看,只存在兩種不同的類加載器:一種是啓動類加載器,這個類加載起使用C++語言實現,是虛擬機自身的一部分;另一種是其他所有的類加載器,這些類加載器都由Java實現,獨立存在於虛擬機外部,並且全部繼承自抽象類java.lang.ClassLoader。

當一個類加載器被調用了loadClass之後,它並不會直接將其加載,而是先交給當前類加載器的父加載器直到最頂層的父加載器,然後再依次向下進行加載。

當前類在沒有父類加載器的情況下,會直接使用啓動類加載器對該類進行加載

1. 破壞雙清委託機制

JDK提供的雙親委託機制並非一個強制的模型,程序開發人員是可以對其靈活發揮破壞這種委託機制的,比如我們想要在程序運行時進行某個模塊功能的升級,甚至是在不停止服務的前提下增加新的功能,這就是我們常說的熱部署。熱部署首先要卸載掉加載該模塊的所有Class的類加載器,卸載類加載器會導致所有類的卸載,很顯然我們無法對JVM三大內置加載器進行卸載,我們只能通過控制自定義類加載器才能做到這一點。

2. 類加載命名空間

每一個類加載器實例都有各自的命名空間,命名空間是由該加載器極其所有的父加載器所構成的,因此在每個類加載器中同一個class都是獨一無二的。

public class NameSpace {

    public static void main(String[] args) throws ClassNotFoundException{

        // 獲取系統類加載器
        ClassLoader classLoader = NameSpace.class.getClassLoader();
        Class<?> aClass = classLoader.loadClass("com.spring.master.java.loader.NameSpace");
        Class<?> bClass = classLoader.loadClass("com.spring.master.java.loader.NameSpace");

        System.out.println(aClass.hashCode());
        System.out.println(bClass.hashCode());
        System.out.println(aClass == bClass);
    }
}

輸出:
1338668845
1338668845
true

使用不同的類加載器,或者同一個類加載器的不同實例,去加載同一個class,則會在堆內存和方法區產生多個class的對象。

在類加載器進行加載的時候,首選會到加載記錄表也就是緩存中,查看該類是否已經被加載過了,如果已經被加載過了,就不會重複加載,否則將會認爲其實首次加載。

同一個class實例在JVM中存在一份這樣的說法是不夠的嚴謹的,跟準確的說應該是同一個class實例在同一個類加載器命名空間之下是唯一的

3. 運行時包

我們在編寫代碼的時候通常會給一個類指定一個包名,包的作用是爲了組織類,防止不同包下同樣名稱的class引起衝突,還能起到封裝的作用,包名和類名構成了類的全限定名稱。在JVM運行時class會有一個運行時包,運行時包是由類加載器的命名空間和類的全限定名稱共同組成的。

比如MyClassLoaderTest的運行時包:
BootStrapClassLoader.ExtClassLoader.ApplicationClassLoader.MyClassLoader.com.spring.master.java.loader.MyClassLoaderTest

4.初始類加載器

由於運行時包的存在,JVM規定了不同的運行時包下的類彼此之間是不可以進行訪問的,那麼問題來了,爲什麼我們在開發的程序中可以訪問java.lang包下的類呢?

每個類在經過ClassLoader的加載之後,在虛擬機中都會有對應Class實例,如果某個類C被加載器CL加載,那麼CL就被稱爲C的初始類加載器。JVM爲每個類加載器維護了一個列表,該列表中記錄了將該類加載器作爲初始類加載器的所有class,在加載一個類時,JVM使用這些列表來判斷該類是否已經被加載過了是否需要首次加載。

根據JVM規範的規定,在類的加載過程中,所有參與的類加載器,即使沒有親自加載過該類,也都會被標識爲該類的初始類加載器,比如java.lang.String首先經過了BrokerDelegateClassLoader類加載器,依次又經過系統類加載器,擴展類加載器,啓動類加載器,這些類加載器都是java.lang.String的初始類夾雜器,JVM會在每個類加載器維護的列表中添加該class類型。

5. 類的卸載

在JVM的啓動過程中,JVM會加載很多的類,在運行期間也會加載很多類,比如自定義的類加載器進行類的加載。

關係JVM在運行期間到底加載了多少的class,可以在啓動JVM時指定-verbose:class參數觀察得到,我們知道某個對象在堆內存中如果沒有其他地方的引用則會在垃圾回收器線程進行GC的時候被回收掉,那麼該對象在堆內存中的Class對象以及Class方法區中的數據結構如何被回收呢?

JVM規定了一個Class只有在滿足以下三個條件的時候纔會被GC回收,也就是類被卸載。

  • 該類所有的實例都已經被GC,比如Simple.class的所有Simple實例都被回收掉
  • 加載該類的ClassLoader實例被回收
  • 該類的class實例沒有再其他地方被引用
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章