亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

重新對Java的類加載器的學習方式

 更新時間:2025年05月13日 16:12:47   作者:找不到、了  
這篇文章主要介紹了重新對Java的類加載器的學習方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教

Java 類加載器JVM 類加載器是同一體系中的不同概念。

Java 類加載器是一個更高抽象層面的概念,可以實現(xiàn)自定義加載器;而 JVM 類加載器是 JVM 的實現(xiàn)部分,負責實際的字節(jié)碼加載。

類加載器:負責將.class文件加載到內存中,并為之生成對應的Class對象(是JVM執(zhí)行類加載機制的前提)

1、介紹

1.1、簡介

在Java中,類加載器(ClassLoader)是Java虛擬機(JVM)用來加載類的核心組件。

它負責將Java字節(jié)碼文件(.class文件)動態(tài)加載到內存中,并將其轉化為JVM可以執(zhí)行的類對象。類加載器是Java運行時系統(tǒng)的一部分,它支持Java的動態(tài)特性,使得Java程序可以在運行時加載類和接口。

如下圖所示:

1.2、符號引用和直接引用

關于符號引用和直接引用的介紹這里先進行一個理解,下面在類加載器執(zhí)行過程的連接階段,有個解析的過程需要聯(lián)系到這里的知識。

  • 符號引用:指的是一個字符串,該字符串表示一個類、字段或方法的名稱,這個名稱由 JVM 在運行時解析。
  • 直接引用:指的是內存中對象的具體地址或偏移量,它用于直接訪問該字段或調用該方法。

1、符號引用

符號引用通常在 Java 字節(jié)碼(class 文件)中以字符串的形式出現(xiàn)。它是類與類之間一種相對的、靈活的引用方式。這種引用方式在字節(jié)碼編譯時就已經確定,但實際內存地址在運行時才會分配。

示例:符號引用的特點

考慮一下這段代碼:

class Example {
    void hello() {
        System.out.println("Hello, World!");
    }
}

在編譯成字節(jié)碼后,hello 方法的符號引用會包含:

  • 方法名:hello
  • 方法描述符:()V(意味著無參數(shù)且沒有返回值)

在字節(jié)碼中的常量池部分,可以看到以下條目(這里是簡化的表示):

1. Class: 'Example'
2. Method: 'hello' with descriptor '()V'

這個符號引用不會包含任何內存地址或具體實現(xiàn)細節(jié)。

2、直接引用

當 JVM 運行時解析符號引用時,它會將符號引用轉換為直接引用。直接引用是指向內存中對象或方法入口的確切地址,這樣 JVM 就可以直接訪問它們。

示例:直接引用的獲取過程

下面的例子展示了符號引用到直接引用的過程。

public class Main {
    public static void main(String[] args) {
        Example example = new Example(); // 創(chuàng)建 Example 對象
        example.hello(); // 調用 hello 方法
    }
}

class Example {
    void hello() {
        System.out.println("Hello, World!");
    }
}

在這個代碼中:

  1. example.hello() 是調用 hello 方法。在編譯時,hello 方法的引用是符號引用。
  2. 當 JVM 到達這行代碼時,它首先會解析 hello 的符號引用。

3、符號轉直接的過程

1.指向類定義

在符號引用查找過程中,JVM 會查找并確認 Example 類的符號引用,即它的名稱 Example。

2.處理方法

一旦 Example 類被確認可用,JVM 將查找 hello 方法的符號引用。當它找到這個符號引用時,它會定位到方法在內存中的地址(也就是直接引用)。

這個直接引用通常是方法在內存中相對于類對象的偏移。

3.執(zhí)行調用

最后,JVM 使用這個直接引用來調用 hello 方法,從而直接在內存中找到并執(zhí)行方法的字節(jié)碼。

2、加載流程

Java是一個動態(tài)語言,這意味著類在程序運行時被加載,而不是在編譯時完成加載。類加載器的主要任務就是將類的字節(jié)碼文件從文件系統(tǒng)或網絡等資源加載到內存中。

具體而言,類加載器的職責包括:

  • 加載類:將Java字節(jié)碼文件讀取到內存,并轉換為Class對象。
  • 鏈接類:將類的二進制數(shù)據合并到JVM運行時環(huán)境中。這一步包括驗證、準備和解析。
  • 初始化類:執(zhí)行類的靜態(tài)初始化塊和靜態(tài)變量的初始化。

關于上述各個階段的主要流程下面進行了詳細的介紹。

由上圖可知:

ClassLoader在整個裝載階段,只能影響到類的加載,而無法通過ClassLoader去改變類的鏈接和初始化行為。

整個執(zhí)行過程可以分為三大步:加載、連接、初始化。

1.加載:將字節(jié)碼文件通過IO流讀取到JVM的方法區(qū),并同時在堆中生成Class對像。

2.鏈接:

  • 驗證:校驗字節(jié)碼文件的正確性。
  • 準備:為類的靜態(tài)變量分配內存,并初始化為默認值;對于final static修飾的變量,在編譯時就已經分配好內存了。
  • 解析:將類中的符號引用轉換為直接引用。

注意:如果類加載后,未通過驗證,則不能被使用。

3.初始化:對類的靜態(tài)變量和靜態(tài)代碼塊初始化為指定的值,執(zhí)行靜態(tài)代碼。

  • ClassLoader是Java的核心組件,所有的Class都是由ClassLoader進行加載的。
  • ClassLoader是否可以運行,則由Execution Engine決定。
  • ClassLoader負責通過各種方式將Class信息的二進制數(shù)據流讀入JVM內部,轉換為一個與目標類對應的java.lang.Class對象實例,然后交給Java虛擬機進行鏈接、初始化等操作。

3、類加載的分類

分為顯式加載 vs 隱式加載(即JVM加載class文件到內存的方式)

3.1、顯示加載:

在代碼中顯示調用ClassLoader加載class對象。

實現(xiàn)方式

  • 1.Class.forName(name)
  • 2.this.getClass().
  • 3.getClassLoader().loadClass() 。

加載class對象。

3.2、隱式加載:

通過虛擬機自動加載到內存中,是不直接在代碼中調用ClassLoader的方法加載class對象,類在被引用(如調用靜態(tài)方法或訪問靜態(tài)字段)時自動加載。

如在加載某個類的class文件時,該類的class文件中引用了另外一個類的對象,此時額外引用的類將通過JVM自動加載到內存中。

代碼示例:

// 隱式加載
User user = new User();
// 顯式加載,并初始化
Class clazz = Class.forName("com.test.java.User");
// 顯式加載,但不初始化
ClassLoader.getSystemClassLoader().loadClass("com.test.java.Parent"); 

心得:

  • 隱式加載:為開發(fā)者簡化了加載過程,不需要顯式調用,通常在程序中不易察覺。
  • 顯式加載:可用于動態(tài)加載類,靈活控制加載時機。

4、命名空間

命名空間指的是在一定范圍內,標識符(如類名、變量名等)被唯一綁定到一個特定的實體。對于類加載器而言,命名空間是指每個類加載器有其各自的發(fā)現(xiàn)和加載類的范圍,它負責自己加載的類及其依賴關系。

4.1、類加載器和命名空間的關系

1.隔離性

每個類加載器都有一個獨立的命名空間。相同類名的類可以在不同的類加載器中存在,而彼此不會干擾。

例如,一個 JAR 中的 com.example.MyClass 可以通過不同的類加載器各自加載,而不會發(fā)生沖突。

2.父類優(yōu)先原則

當一個類加載器加載類時,它會首先將加載請求委托給它的父類加載器。這種機制確保了核心類庫得以優(yōu)先加載,從而避免了相同名稱的類在不同上下文中出現(xiàn)。

4.2、示例

以下是一個簡單的示例,展示不同類加載器之間的命名空間如何影響類的加載。

MyClass.java:

package com.example;

public class MyClass {
    static {
        System.out.println("MyClass loaded!");
    }
}

CustomClassLoader.java:

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

public class CustomClassLoader extends ClassLoader {
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        String filePath = name.replace('.', File.separatorChar) + ".class";
        try (FileInputStream fis = new FileInputStream(new File(filePath))) {
            byte[] b = new byte[fis.available()];
            fis.read(b);
            return defineClass(name, b, 0, b.length);
        } catch (IOException e) {
            throw new ClassNotFoundException("Class not found: " + name, e);
        }
    }
}

Main.java:

public class Main {
    public static void main(String[] args) {
        try {
            CustomClassLoader loader1 = new CustomClassLoader();
            CustomClassLoader loader2 = new CustomClassLoader();

            // 使用兩個自定義類加載器加載同一類
            Class<?> class1 = loader1.loadClass("com.example.MyClass");
            Class<?> class2 = loader2.loadClass("com.example.MyClass");

            // 檢查兩個類加載器加載的類是否相同
            System.out.println("Are class1 and class2 the same? " + (class1 == class2));

            // 創(chuàng)建實例
            Object instance1 = class1.getDeclaredConstructor().newInstance();
            Object instance2 = class2.getDeclaredConstructor().newInstance();

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

當運行 Main.java 時,輸出可能會是:由于加載器不同。

MyClass loaded!
MyClass loaded!
Are class1 and class2 the same? false

解釋

  • 雙重加載:由于我們使用了兩個不同的自定義類加載器 (loader1loader2) 來加載同一個類 com.example.MyClass,因此它們在內存中生成了兩個不同的 Class 實例。
  • 命名空間隔離class1class2 雖然指向同一個類的符號引用,但由于在不同的類加載器中加載,它們擁有獨立的命名空間,因此 class1 == class2 結果為 false

總結

  • 命名空間:為每個類加載器創(chuàng)建了一個獨立的命名空間,使得相同名稱的類可以共存于不同的上下文中。
  • 父類優(yōu)先原則:用于提升類加載的安全性,優(yōu)先通過父類加載器查找類。
  • 自定義類加載器:可以實現(xiàn)輕松管理類加載過程,提供更多靈活性和控制權。

5、類加載器的分類

JVM支持兩種類型的類加載器,分別為引導類加載器(Bootstrap ClassLoader)和自定義類加載器(C z ClassLoader)。

  • Bootstrap ClassLoader(啟動類加載器)
  • Extension ClassLoader(擴展類加載器)
  • Application ClassLoader(系統(tǒng)類加載器):

自定義類加載器:擴展 java.lang.ClassLoader

代碼示例:

public class ClassTestLoader extends ClassLoader{
    public static void main(String[] args) throws ClassNotFoundException {
        ClassLoader classLoader = new ClassTestLoader();
        Class<?> clazz = classLoader.loadClass("com.ali.sls.test.Counter");
        System.out.println("class loader:=="+ clazz.getClassLoader());
        System.out.println("class loader:=="+ clazz.getClassLoader().getParent());
        System.out.println("class loader:=="+ clazz.getClassLoader().getParent().getParent());
    }
}

class loader:==sun.misc.Launcher$AppClassLoader@18b4aac2
class loader:==sun.misc.Launcher$ExtClassLoader@6433a2
class loader:==null

6、雙親委派

Java的類加載機制采用了雙親委派模型(Parent Delegation Model)。該模型的核心思想是:當一個類加載器試圖加載某個類時,它會先將這個請求委托給父類加載器,而不是自己直接加載。只有當父類加載器無法找到該類時,才由當前類加載器嘗試加載。

6.1、緩存機制


每個類加載器(包括父類加載器)都會維護一個緩存,用于存儲已經加載過的類。當類加載器收到加載請求時,會首先檢查緩存中是否已經加載過該類。如果已經加載過,則直接返回緩存的類,而不會重新加載。

  • 子類加載器加載的類

子類加載器加載的類會存儲在子類加載器的緩存中,父類加載器無法訪問子類加載器的緩存。

  • 父類加載器加載的類

父類加載器加載的類會存儲在父類加載器的緩存中,子類加載器可以通過雙親委派機制訪問父類加載器的緩存。

因此,如果子類加載器已經加載了某個類,父類加載器不會再次加載該類,因為父類加載器無法感知子類加載器的緩存。

6.2、類的唯一性

在JVM中,類的唯一性是由 類的全限定名 + 類加載器 共同決定的。即使兩個類加載器加載了同一個類的字節(jié)碼,JVM也會將它們視為不同的類。

如果子類加載器加載了一個類,父類加載器再次嘗試加載同一個類,JVM會認為這是兩個不同的類(因為類加載器不同)。這可能導致 類沖突 或 類型轉換異常,因為JVM認為這兩個類是獨立的。

6.3、工作流程:

類加載器接收到加載請求時,首先將請求委派給父類加載器。

如果父類加載器能找到該類,則加載成功;如果父類加載器無法加載該類,則由當前類加載器加載。這種機制確保了Java核心類庫不會被用戶自定義的類加載器替代或覆蓋。

6.4、如何打破

1:自定義類加載器

我們將創(chuàng)建一個自定義的類加載器,它直接加載某個特定包中的類,而不經過父加載器。

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

public class MyClassLoader extends ClassLoader {
    private String classPath;

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

    @Override
    protected Class<?> loadClass(String name) throws ClassNotFoundException {
        // 檢查是否需要執(zhí)行自定義加載
        if (name.startsWith("com.example")) {
            // 這里實際上不調用父類加載器
            return findClass(name);
        }
        // 否則,使用默認的父類加載器
        return super.loadClass(name);
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        String filePath = classPath + File.separator + name.replace('.', File.separatorChar) + ".class";
        try (FileInputStream fis = new FileInputStream(filePath)) {
            byte[] b = new byte[fis.available()];
            fis.read(b);
            return defineClass(name, b, 0, b.length);
        } catch (IOException e) {
            throw new ClassNotFoundException("Class not found: " + name, e);
        }
    }
}

解釋

  • loadClass 方法中,我們直接處理以 com.example 開頭的類,調用 findClass 來加載。而對于其他類,則調用 super.loadClass(name),這表明默認的父類加載器將處理它。

注意:確保 com/example/MyClass.class 文件在 "path/to/classes" 目錄中。

2.使用 Thread 的上下文類加載器

在某些情況下,也可以通過設置當前線程的上下文類加載器(context class loader)來打破雙親委派。

public class ContextClassLoaderExample {
    public static void main(String[] args) {
        // 設置自定義類加載器為當前線程的上下文類加載器
        Thread.currentThread().setContextClassLoader(new MyClassLoader("path/to/classes"));

        // 然后通過上下文類加載器加載類
        ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
        try {
            Class<?> myClass = contextClassLoader.loadClass("com.example.MyClass"); // 直接調用上下文類加載器
            System.out.println("Loaded class: " + myClass.getName());
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

7、類的卸載

Java 的 GC(垃圾回收)會在類引用不存在時進行類的卸載。只有使用 ClassLoader 加載的類,如果其 ClassLoader 被卸載,該類才會被卸載。

類的卸載并不是一個強制性的操作,只有在特定條件下才會發(fā)生。

7.1、卸載條件

類的卸載發(fā)生在以下情況下:

1.類加載器被垃圾回收

當沒有任何引用指向某個類加載器時,該類加載器及其所加載的類有可能被卸載。JVM 可以回收類加載器,并同時卸載它加載的所有類。

2.類及其類加載器均無法到達

類和類加載器不僅沒有引用,而且它們在常量池、棧幀等處也不再被引用時,可以進行卸載。

7.2、觸發(fā)條件

雖然部分類可以在 Java 程序運行時被卸載,但是的確沒有顯式的方法去卸載類,整個卸載過程是由 JVM 的垃圾回收器自動處理。

以下是一些觸發(fā)類卸載的條件。

1.動態(tài)類加載

當使用自定義類加載器動態(tài)加載類時,如果不再有引用指向這個類和類加載器,它們會被視為垃圾對象,從而可能被回收。

2.Classpath 變化

如果應用程序在運行時改變了類路徑(比如加載新版本的同名類),舊的類及其加載器可能被卸載。

以下是一個類卸載的簡單示例:

public class ClassUnloadingExample {
    public static void main(String[] args) {
        CustomClassLoader classLoader = new CustomClassLoader();
        
        try {
            Class<?> clazz1 = classLoader.loadClass("com.example.MyClass");
            
            // 使用 clazz1
            Object instance = clazz1.getDeclaredConstructor().newInstance();
            System.out.println("Class Loaded: " + clazz1.getName());

            // 設置 classLoader 為 null,解除引用
            classLoader = null;

            // 觸發(fā)垃圾回收
            System.gc();
            Thread.sleep(1000); // 確保 GC 有足夠時間運行

            // 這里將不會再有引用指向這個類,可能被卸載
            System.out.println("Unloading classes...");

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

// 自定義類加載器
class CustomClassLoader extends ClassLoader {
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        // 加載類的邏輯
        // 假設類文件框架完整
        return super.findClass(name);
    }
}

8、類加載異常處理

在類加載過程中可能會遇到的異常主要有:

  • ClassNotFoundException: 當請求的類不存在時拋出。
  • NoClassDefFoundError: 類存在但不再可用,通常是因為 class 文件被刪除或 JVM 啟動時未找到。
  • UnsupportedClassVersionError: 由于 Java 版本不兼容導致的錯誤。

總結

通過上述文章的介紹,希望可以幫助開發(fā)者在項目日常中更加清晰了解java類的加載機制原理。

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

相關文章

最新評論