首頁 Java Java基礎 java類別載入器ClassLoader詳解

java類別載入器ClassLoader詳解

Nov 27, 2019 pm 03:35 PM
classloader 類別載入器

java類別載入器ClassLoader詳解

取得ClassLoader的路徑

1. 取得目前類別的ClassLoader

clazz.getClassLoader()
登入後複製

2.取得目前執行緒上下文的ClassLoader

Thread.currentThread().getContextClassLoader();
登入後複製

3. 取得系統的ClassLoader

ClassLoader.getSystemClassLoader()
登入後複製

4. 取得呼叫者的ClassLoader

DriverManager.getCallerClassLoader
登入後複製

#ClassLoader原始碼解析

#(建議學習:Java視訊教學)  

#概述

類別載入器是用來載入類別的對象,ClassLoader是一個抽象類別。如果我們給定了一個類別的二進位名稱,類別載入器應嘗試去定位或產生構成定義類別的資料。典型的策略是將給定的二進位名稱轉換為檔案名,然後去檔案系統中讀取這個檔案名稱所對應的class檔案。

每個Class物件都會包含一個定義它的ClassLoader的一個引用。

陣列類別的Class對象,不是由類別載入器去創建的,而是在Java運行期JVM根據需要自動建立的。對於數組類別的類別載入器來說,是透過Class.getClassLoader()傳回的,與數組當中元素類型的類別載入器是一樣的;如果數組當中的元素類型是一個原生類型,數組類別是沒有類別載入器的【程式碼一】。

應用實作了ClassLoader的子類別是為了擴充JVM動態載入類別的方式。

類別載入器典型情況下時可以被安全管理器所使用去標識安全性域問題。

ClassLoader類別使用了委託模型來尋找類別和資源,ClassLoader的每個實例都會有一個與之關聯的父ClassLoader,當ClassLoader被要求尋找一個類別或資源的時候,ClassLoader實例在自身嘗試尋找類別或資源之前會委託它的父類別載入器去完成。虛擬機器內建的類別載入器,稱為啟動類別載入器,是沒有父載入器的,但是可以作為一個類別載入器的父類別載入器【雙親委託機制】。

支援並發類別載入的類別載入器叫做並行類別載入器,要求在初始化期間透過ClassLoader.registerAsParallelCapable 方法註冊自身,ClassLoader類別預設被註冊為可以並行,但是如果它的子類別也是並行載入的話需要單獨去註冊子類別。

在委託模型不是嚴格的層次化的環境下,類別載入器需要並行,否則類別載入會導致死鎖,因為載入器的鎖在類別載入過程中是一直被持有的。

通常情況下,Java虛擬機以平台相關的形式從本地的檔案系統載入類別,例如在UNIX系統,虛擬機器從CLASSPATH環境所定義的目錄載入類別。
然而,有些類別並不是來自於檔案;它們是從其它來源得到的,例如網絡,或是由應用程式本身建構【動態代理】。定義類別(defineClass )方法會將位元組陣列轉換為Class的實例,而這個新定義類別的實例可以由Class.newInstance建立。

由類別載入器建立的物件的方法和建構方法可能會引用其它的類,為了確定被引用的類,Java虛擬機會呼叫最初建立類別的類別載入器的loadClass方法。

二進位名稱:以字串參數的形式提供給CalssLoader的任一個類別名,必須是一個二進位的名稱,包含以下四種情況

  • "java.lang. String" 正常類別
  • "javax.swing.JSpinner$DefaultEditor" 內部類別
  • "java.security.KeyStore\(Builder\)FileBuilder$1" KeyStore的內部類別Builder的內部類別FileBuilder的第一個匿名內部類別
  • "java.net.URLClassLoader$3$1" URLClassLoader類別的第三個匿名內部類別的第一個匿名內部類別

#代碼一:

public class Test12 {
    public static void main(String[] args) {
        String[] strings = new String[6];
        System.out.println(strings.getClass().getClassLoader());
        // 运行结果:null

        Test12[] test12s = new Test12[1];
        System.out.println(test12s.getClass().getClassLoader());
        // 运行结果:sun.misc.Launcher$AppClassLoader@18b4aac2

        int[] ints = new int[2];
        System.out.println(ints.getClass().getClassLoader());
        // 运行结果:null
    }
}
登入後複製

loadClass方法

#loadClass的原始碼如下,loadClass方法載入擁有指定的二進位名稱的Class,預設依照以下順序尋找類別:

a)呼叫findLoadedClass(String)檢查這個類別是否被載入

b)呼叫父類別載入器的loadClass方法,如果父類別載入器為null,就會調用啟動類別載入器

c)呼叫findClass(String)方法尋找

使用上述步驟如果類別被找到且resolve為true,就會去呼叫resolveClass(Class)方法

protected Class<?> loadClass(String name, boolean resolve)
  throws ClassNotFoundException
{
  synchronized (getClassLoadingLock(name)) {
      // First, check if the class has already been loaded
      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) {
              // If still not found, then invoke findClass in order
              // to find the class.
              long t1 = System.nanoTime();
              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;
  }
}
登入後複製

findClass方法

#

findClass的源码如下,findClass寻找拥有指定二进制名称的类,JVM鼓励我们重写此方法,需要自定义加载器遵循双亲委托机制,该方法会在检查完父类加载器之后被loadClass方法调用,默认返回ClassNotFoundException异常。

protected Class<?> findClass(String name) throws ClassNotFoundException {
    throw new ClassNotFoundException(name);
}
登入後複製

defineClass方法

defineClass的源码如下,defineClass方法将一个字节数组转换为Class的实例。

protected final Class<?> defineClass(String name, byte[] b, int off, int len,
                                     ProtectionDomain protectionDomain)
    throws ClassFormatError
{
    protectionDomain = preDefineClass(name, protectionDomain);
    String source = defineClassSourceLocation(protectionDomain);
    Class<?> c = defineClass1(name, b, off, len, protectionDomain, source);
    postDefineClass(c, protectionDomain);
    return c;
}
登入後複製

自定义类加载器

/**
 * 继承了ClassLoader,这是一个自定义的类加载器
 * @author 夜的那种黑丶
 */
public class ClassLoaderTest extends ClassLoader {
    public static void main(String[] args) throws Exception {
        ClassLoaderTest loader = new ClassLoaderTest("loader");
       Class<?> clazz = loader.loadClass("classloader.Test01");
        Object object = clazz.newInstance();
        System.out.println(object);
        System.out.println(object.getClass().getClassLoader());
    }
    //------------------------------以上为测试代码---------------------------------

    /**
     * 类加载器名称,标识作用
     */
    private String classLoaderName;

    /**
     * 从磁盘读物字节码文件的扩展名
     */
    private String fileExtension = ".class";

    /**
     * 创建一个类加载器对象,将系统类加载器当做该类加载器的父加载器
     * @param classLoaderName 类加载器名称
     */
    private ClassLoaderTest(String classLoaderName) {
        // 将系统类加载器当做该类加载器的父加载器
        super();
        this.classLoaderName = classLoaderName;
    }

    /**
     * 创建一个类加载器对象,显示指定该类加载器的父加载器
     * 前提是需要有一个类加载器作为父加载器
     * @param parent 父加载器
     * @param classLoaderName 类加载器名称
     */
    private ClassLoaderTest(ClassLoader parent, String classLoaderName) {
        // 显示指定该类加载器的父加载器
        super(parent);
        this.classLoaderName = classLoaderName;
    }

    /**
     * 寻找拥有指定二进制名称的类,重写ClassLoader类的同名方法,需要自定义加载器遵循双亲委托机制
     * 该方法会在检查完父类加载器之后被loadClass方法调用
     * 默认返回ClassNotFoundException异常
     * @param className 类名
     * @return Class的实例
     * @throws ClassNotFoundException 如果类不能被找到,抛出此异常
     */
    @Override
    protected Class<?> findClass(String className) throws ClassNotFoundException {
        byte[] data = this.loadClassData(className);
        /*
         * 通过defineClass方法将字节数组转换为Class
         * defineClass:将一个字节数组转换为Class的实例,在使用这个Class之前必须要被解析
         */
        return this.defineClass(className, data, 0 , data.length);
    }

    /**
     * io操作,根据类名找到对应文件,返回class文件的二进制信息
     * @param className 类名
     * @return class文件的二进制信息
     * @throws ClassNotFoundException 如果类不能被找到,抛出此异常
     */
    private byte[] loadClassData(String className) throws ClassNotFoundException {
        InputStream inputStream = null;
        byte[] data;
        ByteArrayOutputStream byteArrayOutputStream = null;

        try {
            this.classLoaderName = this.classLoaderName.replace(".", "/");
            inputStream = new FileInputStream(new File(className + this.fileExtension));
            byteArrayOutputStream = new ByteArrayOutputStream();

            int ch;
            while (-1 != (ch = inputStream.read())) {
                byteArrayOutputStream.write(ch);
            }

            data = byteArrayOutputStream.toByteArray();
        } catch (Exception e) {
            throw new ClassNotFoundException();
        } finally {
            try {
                if (inputStream != null) {
                    inputStream.close();
                }
                if (byteArrayOutputStream != null) {
                    byteArrayOutputStream.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return data;
    }
}
登入後複製

以上是一段自定义类加载器的代码,我们执行这段代码

classloader.Test01@7f31245a
sun.misc.Launcher$AppClassLoader@18b4aac2
登入後複製

可以看见,这段代码中进行类加载的类加载器还是系统类加载器(AppClassLoader)。这是因为jvm的双亲委托机制造成的,private ClassLoaderTest(String classLoaderName)将系统类加载器当做我们自定义类加载器的父加载器,jvm的双亲委托机制使自定义类加载器委托系统类加载器完成加载。

改造以下代码,添加一个path属性用来指定类加载位置:

public class ClassLoaderTest extends ClassLoader {
    public static void main(String[] args) throws Exception {
        ClassLoaderTest loader = new ClassLoaderTest("loader");
        loader.setPath("/home/fanxuan/Study/java/jvmStudy/out/production/jvmStudy/");
        Class<?> clazz = loader.loadClass("classloader.Test01");
        System.out.println("class:" + clazz);

        Object object = clazz.newInstance();
        System.out.println(object);
        System.out.println(object.getClass().getClassLoader());
    }
    //------------------------------以上为测试代码---------------------------------

    /**
     * 从指定路径加载
     */
    private String path;

    ......
    
    /**
     * io操作,根据类名找到对应文件,返回class文件的二进制信息
     * @param className 类名
     * @return class文件的二进制信息
     * @throws ClassNotFoundException 如果类不能被找到,抛出此异常
     */
    private byte[] loadClassData(String className) throws ClassNotFoundException {
        InputStream inputStream = null;
        byte[] data;
        ByteArrayOutputStream byteArrayOutputStream = null;

        className = className.replace(".", "/");

        try {
            this.classLoaderName = this.classLoaderName.replace(".", "/");
            inputStream = new FileInputStream(new File(this.path + className + this.fileExtension));
            byteArrayOutputStream = new ByteArrayOutputStream();

            int ch;
            while (-1 != (ch = inputStream.read())) {
                byteArrayOutputStream.write(ch);
            }

            data = byteArrayOutputStream.toByteArray();
        } catch (Exception e) {
            throw new ClassNotFoundException();
        } finally {
            try {
                if (inputStream != null) {
                    inputStream.close();
                }
                if (byteArrayOutputStream != null) {
                    byteArrayOutputStream.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return data;
    }

    public void setPath(String path) {
        this.path = path;
    }
}
登入後複製

运行一下

class:class classloader.Test01
classloader.Test01@7f31245a
sun.misc.Launcher$AppClassLoader@18b4aac2
登入後複製

修改一下测试代码,并删除工程下的Test01.class文件

public static void main(String[] args) throws Exception {
    ClassLoaderTest loader = new ClassLoaderTest("loader");
   loader.setPath("/home/fanxuan/桌面/");
    Class<?> clazz = loader.loadClass("classloader.Test01");
    System.out.println("class:" + clazz);

    Object object = clazz.newInstance();
    System.out.println(object);
    System.out.println(object.getClass().getClassLoader());
}
登入後複製

运行一下

class:class classloader.Test01
classloader.Test01@135fbaa4
classloader.ClassLoaderTest@7f31245a
登入後複製

分析

改造后的两块代码,第一块代码中加载类的是系统类加载器AppClassLoader,第二块代码中加载类的是自定义类加载器ClassLoaderTest。是因为ClassLoaderTest会委托他的父加载器AppClassLoader加载class,第一块代码的path直接是工程下,AppClassLoader可以加载到,而第二块代码的path在桌面目录下,所以AppClassLoader无法加载到,然后ClassLoaderTest自身尝试加载并成功加载到。如果第二块代码工程目录下的Test01.class文件没有被删除,那么依然是AppClassLoader加载。

再来测试一块代码

public static void main(String[] args) throws Exception {
    ClassLoaderTest loader = new ClassLoaderTest("loader");
    loader.setPath("/home/fanxuan/Study/java/jvmStudy/out/production/jvmStudy/");
    Class<?> clazz = loader.loadClass("classloader.Test01");
    System.out.println("class:" + clazz.hashCode());

    Object object = clazz.newInstance();
    System.out.println(object.getClass().getClassLoader());

    ClassLoaderTest loader2 = new ClassLoaderTest("loader");
    loader2.setPath("/home/fanxuan/Study/java/jvmStudy/out/production/jvmStudy/");
    Class<?> clazz2 = loader2.loadClass("classloader.Test01");
    System.out.println("class:" + clazz2.hashCode());

    Object object2 = clazz2.newInstance();
    System.out.println(object2.getClass().getClassLoader());
}
登入後複製

结果显而易见,类由系统类加载器加载,并且clazz和clazz2是相同的。

class:2133927002
sun.misc.Launcher$AppClassLoader@18b4aac2
class:2133927002
sun.misc.Launcher$AppClassLoader@18b4aac2
登入後複製

再改造一下

public static void main(String[] args) throws Exception {
    ClassLoaderTest loader = new ClassLoaderTest("loader");
    loader.setPath("/home/fanxuan/桌面/");
    Class<?> clazz = loader.loadClass("classloader.Test01");
    System.out.println("class:" + clazz.hashCode());

    Object object = clazz.newInstance();
    System.out.println(object.getClass().getClassLoader());

    ClassLoaderTest loader2 = new ClassLoaderTest("loader2");
    loader2.setPath("/home/fanxuan/桌面/");
    Class<?> clazz2 = loader2.loadClass("classloader.Test01");
    System.out.println("class:" + clazz2.hashCode());

    Object object2 = clazz2.newInstance();
    System.out.println(object2.getClass().getClassLoader());
}
登入後複製

运行结果

class:325040804
classloader.ClassLoaderTest@7f31245a
class:621009875
classloader.ClassLoaderTest@45ee12a7
登入後複製

ClassLoaderTest是显而易见,但是clazz和clazz2是不同的,这是因为类加载器的命名空间的原因。

我们可以通过设置父类加载器来让loader和loader2处于同一命名空间

public static void main(String[] args) throws Exception {
    ClassLoaderTest loader = new ClassLoaderTest("loader");
    loader.setPath("/home/fanxuan/桌面/");
    Class<?> clazz = loader.loadClass("classloader.Test01");
    System.out.println("class:" + clazz.hashCode());

    Object object = clazz.newInstance();
    System.out.println(object.getClass().getClassLoader());

    ClassLoaderTest loader2 = new ClassLoaderTest(loader, "loader2");
    loader2.setPath("/home/fanxuan/桌面/");
    Class<?> clazz2 = loader2.loadClass("classloader.Test01");
    System.out.println("class:" + clazz2.hashCode());

    Object object2 = clazz2.newInstance();
    System.out.println(object2.getClass().getClassLoader());
}
登入後複製

运行结果

class:325040804
classloader.ClassLoaderTest@7f31245a
class:325040804
classloader.ClassLoaderTest@7f31245a
登入後複製

扩展:命名空间

1. 每个类加载器都有自己的命名空间,命名空间由该加载器及所有的父加载器所加载的类组成

2. 在同一命名空间中,不会出现类的完整名字(包括类的包名)相同的两个类

3. 在不同的命名空间中,有可能会出现类的完整名字(包括类的包名)相同的两个类

php中文网,大量的免费Java入门教程,欢迎在线学习!  

以上是java類別載入器ClassLoader詳解的詳細內容。更多資訊請關注PHP中文網其他相關文章!

本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

Java開發中如何解決類別載入器衝突問題 Java開發中如何解決類別載入器衝突問題 Jun 29, 2023 am 08:32 AM

Java開發中如何解決類別載入器衝突問題引言:在Java開發中,類別載入器衝突是一個常見的問題。當使用不同的類別載入器載入同一個類別或資源檔案時,就會出現衝突,導致程式無法正常運作。本文將介紹什麼是類別載入器衝突,以及如何解決這個問題。一、什麼是類別載入器衝突Java中的類別載入機制採用了雙親委派模型,每個類別載入器都有一個父類別載入器,最終的父類別載入器是啟動類別載入器。當需要

Java錯誤:ClassLoader錯誤,如何解決與避免 Java錯誤:ClassLoader錯誤,如何解決與避免 Jun 25, 2023 am 09:02 AM

在使用Java開發過程中,我們常會遇到ClassLoader錯誤,這是因為類別載入器沒有找到或載入到正確的類別所致。這種錯誤會導致程式無法正常執行,造成開發不便。因此,我們需要了解ClassLoader錯誤的成因及其解決方法,以便更好地優化我們的Java程式。一、ClassLoader錯誤的成因Java中透過ClassLoader來載入類,ClassLoade

SpringBoot怎麼透過自訂classloader加密保護class文件 SpringBoot怎麼透過自訂classloader加密保護class文件 May 11, 2023 pm 09:07 PM

背景最近針對公司框架進行關鍵業務代碼進行加密處理,防止透過jd-gui等反編譯工具能夠輕鬆還原工程代碼,相關混淆方案配置使用比較複雜且針對springboot項目問題較多,所以針對class文件加密再通過自訂的classloder進行解密加載,此方案並不是絕對安全,只是加大反編譯的困難程度,防君子不防小人,整體加密保護流程圖如下圖所示maven插件加密使用自訂maven插件對編譯後指定的class檔案進行加密,加密後的class檔案拷貝到指定路徑,這裡是儲存到resource/corecla

Java中如何使用ClassLoader函數進行類別載入 Java中如何使用ClassLoader函數進行類別載入 Jun 26, 2023 pm 04:16 PM

Java中使用ClassLoader函數進行類別載入的原理和方法一直是Java開發者關注的焦點之一。 ClassLoader函數是Java類別函式庫的一部分,它主要的作用是將Java類別檔案載入到Java虛擬機器(JVM)中,讓程式能夠正常運作。 ClassLoader函數是Java類別載入的核心,在Java的執行環境中,它負責尋找和載入Java類別的字節碼,所以了解並掌握

Java中如何使用ClassLoader函數進行類別動態載入 Java中如何使用ClassLoader函數進行類別動態載入 Jun 26, 2023 pm 02:10 PM

Java中的ClassLoader函數可以實現在運行時動態載入類,這在一些需要靈活地部署和修改程式碼的應用程式中非常有用。透過ClassLoader的功能,可以實現插件機制,提升系統的可擴充性和靈活性。本文將介紹如何使用ClassLoader函數進行類別動態載入。一、ClassLoader的作用在啟動Java虛擬機器(JVM)時,會建立三個ClassLoader:

Java虛擬機器中類別載入器的作用 Java虛擬機器中類別載入器的作用 Apr 13, 2024 pm 02:51 PM

類別載入器的作用:載入:從指定來源讀取類別檔案。驗證:確認類別文件符合規範。準備:分配內存,初始化靜態變數。解析:解析符號引用。初始化:呼叫方法,執行靜態初始化區塊,分配類別物件。

PHP 自動載入知識點詳解:解鎖提高技能門檻的利器 PHP 自動載入知識點詳解:解鎖提高技能門檻的利器 Feb 19, 2024 pm 03:15 PM

PHP自動載入概述php自動載入是指在使用一個類別時,PHP會自動載入該類別的定義檔。這通常透過類別載入器來實現。類別載入器是一個負責載入類別定義檔的程序,它可以是內建的,也可以是自訂的。類別載入器類型PHP內建的類別載入器有兩種:Zend類別載入器:這是PHP的預設類別載入器,它會載入位於PHP內建程式庫中的類別定義檔。 PSR-4類別載入器:PSR-4是一個自動載入標準,它定義了一套載入類別定義檔的規則。 PSR-4類載入器會根據PSR-4標準載入類別定義檔。此外,還可以自訂類別載入器。自訂類別載入器可以根據自己

PHP 自動載入的藝術:探索載入技術 PHP 自動載入的藝術:探索載入技術 Mar 02, 2024 pm 09:19 PM

自動載入是一種技術,可以自動載入PHP類,而無需手動包含每個檔案。它簡化了應用程式的開發和維護,提高了效能和可維護性。本文將探討php常用的自動載入技術。 SPLAutoloaderSPL(標準PHP函式庫)包含一個內建的自動載入機制,稱為spl_autoload_reGISter()函式。此函數允許您註冊一個載入器函數,當嘗試載入一個不存在的類別時,該函數將呼叫該載入器函數。以下範例示範如何使用SPLAutoloader:spl_autoload_register(function($class){

See all articles