欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Java類加載器ClassLoader詳解

 更新時間:2025年06月11日 10:45:03   作者:雷神樂樂  
這篇文章主要介紹了Java類加載器ClassLoader,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教

以下文章都是基于JDK1.8環(huán)境

一、類的加載過程

JDK8引用的都是jar包

JDK11引用的都是model

我們編寫的".java"文件需要通過javac編譯成".class"文件,而程序運行時,JVM會把".class"文件加載到內(nèi)存中,并創(chuàng)建對應(yīng)的class對象,這個過程被稱為類的加載。

簡單來說:將class文件讀入內(nèi)存,并為之創(chuàng)建一個Class對象。

JVM運行的是class文件,不單單是java語言,無論哪種語言編寫的,只要文件是.class類型的,就能夠在JVM上運行。

學習類加載器的目的:使用類加載器可以讓我們得代碼7*24小時,不間斷地運行。即修改代碼后無需重啟就能生效,類似于熱部署。

二、默認的類加載器

JVM通過類加載器把“.class”文件加載到內(nèi)存中,默認有 3 個類加載器,分別是:

  • Bootstrap ClassLoader     啟動類加載器
  • ExtClassLoader               擴展類加載器
  • AppClassLoader              系統(tǒng)類加載器(應(yīng)用類加載器)

三個類加載器各有不同的作用

(一)Bootstrap ClassLoader1.基本介紹

作用:加載JDK核心類庫(String,Integer,Long,ArrayList等等)

方式:加載某個類時,在指定的路徑中(Jar包或文件夾)搜索這個類,如果搜到就加載,如果沒搜到,報:ClassNotFoundException

搜索路徑:由 sun.boot.class.path 所指定的,比如:%JRE_HOME%\jre\lib下的rt.jar、resources.jar、charsets.jar等,也就是JDK核心類庫,其中 rt.jar 里面就存放著常用的 JAVA API

代碼演示:

public static void main(String[] args) {
    // 輸出String類是被哪個類加載器,加載到內(nèi)存中
    System.out.println(String.class.getClassLoader());
    // 獲取系統(tǒng)配置
    // bootstrap生效的時候會去sun.boot.class.path路徑下找對應(yīng)的類
    String paths = System.getProperty("sun.boot.class.path");
    // windows下使用;分隔
    // linux使用:分隔
    String[] arr = paths.split(";");
    for (String s : arr) {
        System.out.println(s);
    }
}

返回結(jié)果:

為什么String.class.getClassLoader() 返回null?

原因是:Bootstrap ClassLoader是由C/C++編寫的,是虛擬機的一部分,并不是JAVA中的類,所以無法在 Java 代碼中獲取它的引用,因此返回 null。

因此:如果一個類(System.out.println(String.class.getClassLoader());)輸出為null,說明該類是被Bootstrap ClassLoader加載的。前提是基于JDK1.8環(huán)境。

Bootstrap ClassLoader加載String類會去下面這些類中尋找:

最后一行不是默認路徑,是IDEA默認添加的,紅框部分才是默認加載路徑。

String類在rt包下,按照上圖的順序依次向下找,在rt包中找到后,就不會繼續(xù)向下尋找了。

2.自定義路徑

可通過 -Xbootclasspath 參數(shù)修改 Bootstrap ClassLoader 的搜索路徑

用法

含義

備注

-Xbootclasspath:路徑

指定的路徑會完全取代jdk核心的搜索路徑

堅決不要用

-Xbootclasspath/a:路徑

指定的路徑會在jdk核心類后搜索

可用

-Xbootclasspath/p:路徑

指定的路徑會在jdk核心類前搜索

可用,不建議使用

注意:如果配置多個路徑,linux/unix下用“:”分割,windows下用“;”分割。

代碼演示:在 pom.xml 中添加 commons-io,同時也要把這個jar包放到D:\test 中

<dependency>
    <groupId>commons-io</groupId>
    <artifactId>commons-io</artifactId>
    <version>2.8.0</version>
</dependency>

添加JVM參數(shù),指定尋找的包路徑在jdk核心類后搜索

-Xbootclasspath/a:D:\test\commons-io-2.8.0.jar

去這個包下尋找ByteOrderMark這個類

修改代碼:

public static void main(String[] args) {
    // 輸出String類是被哪個類加載器,加載到內(nèi)存中
    // System.out.println(String.class.getClassLoader());
    System.out.println(ByteOrderMark.class.getClassLoader());
    // 獲取系統(tǒng)配置
    // bootstrap生效的時候會去sun.boot.class.path路徑下找對應(yīng)的類
    String paths = System.getProperty("sun.boot.class.path");
    // windows下使用;分隔
    // linux使用:分隔
    String[] arr = paths.split(";");
    for (String s : arr) {
        System.out.println(s);
    }
}

運行結(jié)果:

Bootstrap ClassLoader在JDK核心類中找不到就會去指定的路徑下尋找。

(二)ExtClassLoader

1.基本使用

擴展類加載器,加載擴展類庫,搜索路徑由-Djava.ext.dirs指定,比如:%JRE_HOME%\jre\lib\ext目錄下的jar包和class文件。即ExtClassLoader加載類的時候會去下面的路徑尋找,找不到就會報錯。

代碼演示:

public static void main(String[] args) {
    // 輸出String類是被哪個類加載器,加載到內(nèi)存中
    // System.out.println(String.class.getClassLoader());
    // System.out.println(ByteOrderMark.class.getClassLoader());
    System.out.println(DNSNameService.class.getClassLoader());
    // 獲取系統(tǒng)配置
    // 輸出 ExtClassLoader 掃描的路徑,注意:輸出的是文件夾
    String paths = System.getProperty("java.ext.dirs");
    // windows下使用;分隔
    // linux使用:分隔
    String[] arr = paths.split(";");
    for (String s : arr) {
        System.out.println(s);
    }
}

運行結(jié)果:

sun.misc.Launcher$ExtClassLoader@383534aa

有$符號表示ExtClassLoader是Launcher這個類的內(nèi)部類。只要出現(xiàn)$就說明后面是前面的內(nèi)部類

搜索ExtClassLoader后,發(fā)現(xiàn)ExtClassLoader確實是個內(nèi)部類,并且還是靜態(tài)的。

2.配置指定路徑

-Djava.ext.dirs=D:\test

public static void main(String[] args) {
    // 輸出String類是被哪個類加載器,加載到內(nèi)存中
    // System.out.println(String.class.getClassLoader());
    System.out.println(ByteOrderMark.class.getClassLoader());
    // System.out.println(DNSNameService.class.getClassLoader());
    // 獲取系統(tǒng)配置
    // 輸出 ExtClassLoader 掃描的路徑,注意:輸出的是文件夾
    String paths = System.getProperty("java.ext.dirs");
    // windows下使用;分隔
    // linux使用:分隔
    String[] arr = paths.split(";");
    for (String s : arr) {
        System.out.println(s);
    }
}

代碼中表示,讓ExtClassLoader去D:\test文件夾下尋找ByteOrderMark這個類。

運行結(jié)果:

自定義路徑會把默認路徑覆蓋掉,所以這種方法不建議使用,了解即可。

(三)AppClassLoader

AppClassLoader也叫SystemClassLoader(系統(tǒng)類加載器),搜索路徑由java.class.path(CLASSPATH) 指定。加載項目中自己寫的類 第三方依賴包。

public static void main(String[] args) {
    // 輸出String類是被哪個類加載器,加載到內(nèi)存中
    // System.out.println(String.class.getClassLoader());
    // System.out.println(ByteOrderMark.class.getClassLoader());
    // System.out.println(DNSNameService.class.getClassLoader());
    System.out.println(Main.class.getClassLoader());
 
    // 獲取系統(tǒng)配置
    // 輸出 ExtClassLoader 掃描的路徑,注意:輸出的是文件夾
    String paths = System.getProperty("java.class.path");
    // windows下使用;分隔
    // linux使用:分隔
    String[] arr = paths.split(";");
    for (String s : arr) {
        System.out.println(s);
    }
}

運行結(jié)果:

最下面的兩行是IDEA自帶的,無需理會

由此可以看出,IDEA在運行java代碼時,主動給我們添加了很多路徑(JDK核心類庫、擴展類庫、自己寫的類和第三方依賴包到classpath中),所以才會打印如此多的路徑。如下圖。

(四)類加載器的初始化

1.源碼跟蹤

從上面的輸出可以發(fā)現(xiàn) ExtClassLoader、AppClassLoader都是 Java 對象,接下來看一下它們是如何創(chuàng)建的。

這兩個對象的生成都是在 Launcher 中完成的:Launcher類是 java 程序的入口,在啟動 java 應(yīng)用的時候會首先創(chuàng)建Launcher類的對象,創(chuàng)建Launcher類的時候會創(chuàng)建ExtClassLoader、AppClassLoader。

Launcher類太過于底層,所以無法打斷點,構(gòu)造方法如下:

首先,創(chuàng)建 ExtClassLoader

ExtClassLoader對象創(chuàng)建成功后,將器傳入AppClassLoader中

點進這個getAppClassLoader靜態(tài)方法:

super一直點到最上層,在這里,parent參數(shù)就是傳入的 ExtClassLoader。

由此可以看出:ExtClassLoader是AppClassLoader的父類加載器。

2.ExtClassLoader和AppClassLoader的關(guān)系結(jié)論——父類加載器

在 Java 中,AppClassLoader 和 ExtClassLoader 都是由 sun.misc.Launcher 類創(chuàng)建的。盡管它們的名字中包含“ClassLoader”,但它們并不是通過繼承關(guān)系來定義父子關(guān)系的,而是通過設(shè)置父加載器的方式來實現(xiàn)的。這意味著 AppClassLoader 實際上是將 ExtClassLoader 作為其父類加載器,而不是通過類繼承的方式。

只有類之間才會存在繼承關(guān)系,我們這里說的是AppClassLoader 和 ExtClassLoader對象。

在 Java 類加載器的上下文中,“父類加載器”這個術(shù)語并不意味著類加載器之間存在繼承關(guān)系(即它們不是通過 extends 關(guān)鍵字定義的父子類關(guān)系),而是指類加載器之間的委派關(guān)系。具體來說,AppClassLoader 將 ExtClassLoader 作為其父類加載器,指的是當 AppClassLoader 需要加載某個類時,它首先會請求 ExtClassLoader(它的父類加載器)嘗試加載該類。這種機制是基于“雙親委派模型”的。

因此,AppClassLoader的父類是URLClassLoader;父類加載器是ExtClassLoader。

通過debug可以看出來,AppClassLoader的parent屬性是ExtClassLoader,ExtClassLoader的parent屬性是BootstrapClassLoader,前文提過,顯示null,就說明是BootstrapClassLoader。

三、雙親委派模式

(一)概念介紹

雙親委派模式是Java類加載機制的核心原理,用于規(guī)范類加載器之間的協(xié)作方式。其核心思想是:當一個類加載器收到類加載請求時,不會直接自己加載,而是將請求委派給父類加載器,只有當父類加載器無法加載時,才會由當前類加載器自己嘗試加載。

如果父加載器可以完成加載任務(wù),就成功返回;倘若父加載器無法完成此加載任務(wù),子加載器才會嘗試自己去加載。

(二)源碼解釋

加載一個類,一定先從 Launcher.AppClassLoader 的 loadClass 方法開始。

protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
    // 1. 檢查是否已加載
    Class<?> c = findLoadedClass(name);
    if (c == null) {
        try {
            // 2. 委派給父類加載器(遞歸向上)
            if (parent != null) {
                c = parent.loadClass(name, false);
            } else {
                c = findBootstrapClassOrNull(name);
            }
        } catch (ClassNotFoundException e) {
            // 父類加載器未找到,繼續(xù)執(zhí)行
        }
        if (c == null) {
            // 3. 自己嘗試加載
            c = findClass(name);
        }
    }
    if (resolve) {
        resolveClass(c);
    }
    return c;
}

(三)雙親委派模式的核心優(yōu)勢

  • 安全性:確保核心類(如java.lang.Object)只能由啟動類加載器加載,防止用戶自定義類覆蓋JDK核心類,避免惡意代碼篡改。保證java核心api不會被隨意替換,專業(yè)說法:沙箱安全機制。
  • 唯一性:同一名稱的類只會被加載一次(由首個能加載它的類加載器完成),確保類實例的兼容性。
  • 層級隔離:不同類加載器加載的類空間隔離,例如Tomcat的多個Web應(yīng)用可通過不同類加載器隔離類版本。

代碼示例:

自定義一個Integer類:

package java.lang;
 
public class Integer {
    public static void main(String[] args) {
        System.out.println("運行自定義的Integer類......");
    }
}

運行結(jié)果:

原因是BootStrap ClassLoader已經(jīng)在核心類庫中找到j(luò)ava.lang.Integer類了,所以不會再加載自定義的java.lang.Integer類了,這充分體現(xiàn)了JDK核心類不會被覆蓋的優(yōu)勢。

四、動態(tài)加載

(一)URLClassLoader

URLClassLoader是Java中用于從指定的URL(統(tǒng)一資源定位符)加載類和資源的類加載器,屬于java.lang.ClassLoader的子類,也是ExtClassLoader和AppClassLoader的父類。它允許從網(wǎng)絡(luò)或本地文件系統(tǒng)中的目錄、JAR 文件等位置動態(tài)加載類,廣泛應(yīng)用于插件化開發(fā)、熱部署、動態(tài)模塊加載等場景。

常用的構(gòu)造方法:

  • URLClassLoader(URL[] urls, ClassLoader parent):使用指定的父加載器創(chuàng)建對象,從指定的urls路徑來查詢、并加載類。
  • URLClassLoader(URL[] urls):使用默認的父加載器(AppClassLoader)創(chuàng)建一個ClassLoader對象,從指定的urls路徑來查詢、并加載類。

(二)實戰(zhàn)

新建一個普通的java項目parse-excel-demo,并打成jar包。

在另一個項目classloader-demo中編寫下面的代碼,即可使用上面的方法

public class RunDemo {
    public static void main(String[] args) throws Exception {
        File file = new File(
                "G:\\develop\\workspace\\four\\" +
                        "newSmProjects\\parse-excel-demo\\target\\" +
                        "parse-excel-demo-1.0-SNAPSHOT.jar");
        URL[] urls = {file.toURI().toURL()};
        URLClassLoader myUrlClassLoader = new URLClassLoader(urls);
 
        Class<?> parseExcel = myUrlClassLoader.loadClass("com.test.excel.ParseExcel");
        Object obj = parseExcel.newInstance();
        Method parse = parseExcel.getMethod("parse");
        parse.invoke(obj);
    }
}

運行結(jié)果:

即使被引用的jar包內(nèi)容被修改,只要路徑正確,RunDemo所在的項目都不需要重啟就能運行:

RunDemo運行結(jié)果:

(三)依賴問題

有些時候,parse-excel-demo中可能引用一些第三方庫,比如:jackson-core-2.11.0.jar

<dependency>
  <groupId>com.fasterxml.jackson.core</groupId>
  <artifactId>jackson-core</artifactId>
  <version>2.11.0</version>
</dependency>

并在代碼中使用:

package com.test.excel;
 
import com.fasterxml.jackson.core.JsonFactory;
 
public class ParseExcel {
    public void parse() {
        System.out.println("開始解析Excel......");
        JsonFactory jsonFactory = new JsonFactory();
        System.out.println("運行其getFormatGeneratorFeatures方法:" +
                jsonFactory.getFormatGeneratorFeatures());
    }
}

修改后重新對 parse 打包,然后運行clasterloader-demo中的代碼,結(jié)果:

問題分析:

myUrlClassLoader的父類加載器是

場景

父類加載器

原因

默認構(gòu)造URLClassLoader(urls)

AppClassLoader

默認使用系統(tǒng)類加載器作為父類加載器。

顯式指定父類加載器

任意(如ExtClassLoader)

可通過構(gòu)造方法URLClassLoader(urls,parent)自定義。

繼承關(guān)系

URLClassLoader是父類

AppClassLoader和ExtClassLoader是URLClassLoader的子類。

根據(jù)雙親委派模式,myUrlClassLoader加載時parseExcel的時候會去加載JsonFactory,加載JsonFactory會交給父類加載器AppClassLoader,AppClassLoader無法加載就會交給其父類加載器ExtClassLoader,ExtClassLoader無法加載又交給自己的父類加載器BootStrap ClassLoader,都找不到才會報錯。

因此,當我們開發(fā)中碰到ClassNotFoundException的排錯方法要考慮類的加載過程。

解決方案:完善代碼,將JsonFactory所屬的包放在myUrlClassLoader的掃描路徑下:

public class RunDemo {
    public static void main(String[] args) throws Exception {
        File file = new File(
                "G:\\develop\\workspace\\four\\" +
                        "newSmProjects\\parse-excel-demo\\target\\" +
                        "parse-excel-demo-1.0-SNAPSHOT.jar");
        File file2 = new File(
                "D:\\apache-maven-3.8.1\\repository\\" +
                        "com\\fasterxml\\jackson\\core\\jackson-core\\2.11.0" +
                        "\\jackson-core-2.11.0.jar");
        URL[] urls = {file.toURI().toURL(), file2.toURI().toURL()};
        URLClassLoader myUrlClassLoader = new URLClassLoader(urls);
 
        Class<?> parseExcel = myUrlClassLoader.loadClass("com.test.excel.ParseExcel");
        Object obj = parseExcel.newInstance();
        Method parse = parseExcel.getMethod("parse");
        parse.invoke(obj);
    }
}

也可以將依賴放入classloader-demo中。AppClassLoader可以成功加載第三方依賴包。

(四)版本沖突問題

我們降低classloader-demo中的依賴版本:

再次運行會報錯:

即使將2.11.0版本放到搜索路徑中還是會報錯,因為AppClassLoader已經(jīng)加載完畢了,myUrlClassLoader就不會再加載了。

而項目中已有的依賴也不能更改,那么如何解決呢?

讓程序同時運行兩個版本的jsonFactory即可。

public class RunDemo {
    public static void main(String[] args) throws Exception {
        File file = new File(
                "G:\\develop\\workspace\\four\\" +
                        "newSmProjects\\parse-excel-demo\\target\\" +
                        "parse-excel-demo-1.0-SNAPSHOT.jar");
        File file2 = new File(
                "D:\\apache-maven-3.8.1\\repository\\" +
                        "com\\fasterxml\\jackson\\core\\jackson-core\\2.11.0" +
                        "\\jackson-core-2.11.0.jar");
        URL[] urls = {file.toURI().toURL(),file2.toURI().toURL()};
        // 創(chuàng)建自定義類加載器
        // 這時候myUrlClassLoader的parent是ExtClassLoader        
        URLClassLoader myUrlClassLoader = new URLClassLoader(urls,RunDemo.class.getClassLoader().getParent());
 
        Class<?> parseExcel = myUrlClassLoader.loadClass("com.test.excel.ParseExcel");
        Object obj = parseExcel.newInstance();
        Method parse = parseExcel.getMethod("parse");
        parse.invoke(obj);
    }
}

總結(jié)

以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。

相關(guān)文章

  • SpringAop實現(xiàn)原理及代理模式詳解

    SpringAop實現(xiàn)原理及代理模式詳解

    Spring的AOP就是通過動態(tài)代理實現(xiàn)的,使用了兩個動態(tài)代理,分別是JDK的動態(tài)代理和CGLIB動態(tài)代理,本文重點給大家介紹下SpringAop實現(xiàn)原理及代理模式,感興趣的朋友一起看看吧
    2022-04-04
  • Java SpringBoot 集成 Redis詳解

    Java SpringBoot 集成 Redis詳解

    Redis 是一個由 Salvatore Sanfilippo 寫的 key-value 存儲系統(tǒng),是跨平臺的非關(guān)系型數(shù)據(jù)庫。Redis 是一個開源的使用 ANSI C 語言編寫、遵守 BSD 協(xié)議、支持網(wǎng)絡(luò)、可基于內(nèi)存、分布式、可選持久性的鍵值對(Key-Value)存儲數(shù)據(jù)庫,并提供多種語言的 API
    2021-10-10
  • SpringMVC體系分層模式原理圖解

    SpringMVC體系分層模式原理圖解

    這篇文章主要介紹了SpringMVC體系分層模式原理圖解,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下
    2020-06-06
  • java編程實現(xiàn)求解八枚銀幣代碼分享

    java編程實現(xiàn)求解八枚銀幣代碼分享

    這篇文章主要介紹了java編程實現(xiàn)求解八枚銀幣代碼分享,具有一定參考價值,需要的朋友可以了解下。
    2017-11-11
  • springbooot整合dynamic?datasource數(shù)據(jù)庫密碼加密方式

    springbooot整合dynamic?datasource數(shù)據(jù)庫密碼加密方式

    這篇文章主要介紹了springbooot整合dynamic?datasource?數(shù)據(jù)庫密碼加密方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2024-01-01
  • 使用lombok@Data存在extends時需要注意的問題

    使用lombok@Data存在extends時需要注意的問題

    在Java編程中,正確實現(xiàn)equals方法是保證對象比較一致性的關(guān)鍵,使用instanceof檢查類型可能導致違反對稱性原則,即當子類和父類都重寫equals時可能出現(xiàn)a.equals(b)不等于b.equals(a)的情況,Lombok的@EqualsAndHashCode注解可以通過callSuper=true參數(shù)
    2024-10-10
  • Java中增強for循環(huán)在一維數(shù)組和二維數(shù)組中的使用方法

    Java中增強for循環(huán)在一維數(shù)組和二維數(shù)組中的使用方法

    下面小編就為大家?guī)硪黄狫ava中增強for循環(huán)在一維數(shù)組和二維數(shù)組中的使用方法。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2016-10-10
  • JDBC中PreparedStatement詳解以及應(yīng)用場景實例介紹

    JDBC中PreparedStatement詳解以及應(yīng)用場景實例介紹

    PreparedStatement對象代表的是一個預編譯的SQL語句,用它提供的setter方法可以傳入查詢的變量,這篇文章主要給大家介紹了關(guān)于JDBC中PreparedStatement詳解以及應(yīng)用場景實例介紹的相關(guān)資料,需要的朋友可以參考下
    2024-02-02
  • httpclient ConnectionHolder連接池連接保持源碼解析

    httpclient ConnectionHolder連接池連接保持源碼解析

    這篇文章主要為大家介紹了httpclient ConnectionHolder連接池連接保持源碼解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-11-11
  • springboot security自定義認證過程

    springboot security自定義認證過程

    這篇文章主要介紹了springboot security自定義認證過程,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2025-03-03

最新評論