Java類載入機制詳解

来源:http://www.cnblogs.com/ww926453/archive/2017/09/21/7568361.html
-Advertisement-
Play Games

一、類載入器 類載入器(ClassLoader),顧名思義,即載入類的東西。在我們使用一個類之前,JVM需要先將該類的位元組碼文件(.class文件)從磁碟、網路或其他來源載入到記憶體中,並對位元組碼進行解析生成對應的Class對象,這就是類載入器的功能。我們可以利用類載入器,實現類的動態載入。 二、類的 ...


一、類載入器

類載入器(ClassLoader),顧名思義,即載入類的東西。在我們使用一個類之前,JVM需要先將該類的位元組碼文件(.class文件)從磁碟、網路或其他來源載入到記憶體中,並對位元組碼進行解析生成對應的Class對象,這就是類載入器的功能。我們可以利用類載入器,實現類的動態載入。

二、類的載入機制

在Java中,採用雙親委派機制來實現類的載入。那什麼是雙親委派機制?在Java Doc中有這樣一段描述:

The ClassLoader class uses a delegation model to search for classes and resources. Each instance of ClassLoader has an associated parent class loader. When requested to find a class or resource, a ClassLoader instance will delegate the search for the class or resource to its parent class loader before attempting to find the class or resource itself. The virtual machine’s built-in class loader, called the “bootstrap class loader”, does not itself have a parent but may serve as the parent of a ClassLoader instance.

從以上描述中,我們可以總結出如下四點: 
1、類的載入過程採用委托模式實現 
2、每個 ClassLoader 都有一個父載入器。 
3、類載入器在載入類之前會先遞歸的去嘗試使用父載入器載入。 
4、虛擬機有一個內建的啟動類載入器(Bootstrap ClassLoader),該載入器沒有父載入器,但是可以作為其他載入器的父載入器。 
Java 提供三種類型的系統類載入器。第一種是啟動類載入器,由C++語言實現,屬於JVM的一部分,其作用是載入 /lib 目錄中的文件,並且該類載入器只載入特定名稱的文件(如 rt.jar),而不是該目錄下所有的文件。另外兩種是 Java 語言自身實現的類載入器,包括擴展類載入器(ExtClassLoader)和應用類載入器(AppClassLoader),擴展類載入器負責載入\lib\ext目錄中或系統變數 java.ext.dirs 所指定的目錄中的文件。應用程式類載入器負責載入用戶類路徑中的文件。用戶可以直接使用擴展類載入器或系統類載入器來載入自己的類,但是用戶無法直接使用啟動類載入器,除了這兩種類載入器以外,用戶也可以自定義類載入器,載入流程如下圖所示: 

註意:這裡父類載入器並不是通過繼承關係來實現的,而是採用組合實現的。 
我們可以通過一段程式來驗證這個過程:

/**
 * Java學習交流QQ群:589809992 我們一起學Java!
 */
public class Test {
}

public class TestMain {
    public static void main(String[] args) {

        ClassLoader loader = Test.class.getClassLoader();
        while (loader!=null){
            System.out.println(loader);
            loader = loader.getParent();
        }
    }
}

上面程式的運行結果如下所示:

從結果我們可以看出,預設情況下,用戶自定義的類使用 AppClassLoader 載入,AppClassLoader 的父載入器為 ExtClassLoader,但是 ExtClassLoader 的父載入器卻顯示為空,這是什麼原因呢?究其緣由,啟動類載入器屬於 JVM 的一部分,它不是由 Java 語言實現的,在 Java 中無法直接引用,所以才返回空。但如果是這樣,該怎麼實現 ExtClassLoader 與 啟動類載入器之間雙親委派機制?我們可以參考一下源碼:

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;
       }
   }

從源碼可以看出,ExtClassLoader 和 AppClassLoader都繼承自 ClassLoader 類,ClassLoader 類中通過 loadClass 方法來實現雙親委派機制。整個類的載入過程可分為如下三步:

1、查找對應的類是否已經載入。 
2、若未載入,則判斷當前類載入器的父載入器是否為空,不為空則委托給父類去載入,否則調用啟動類載入器載入(findBootstrapClassOrNull 再往下會調用一個 native 方法)。 
3、若第二步載入失敗,則調用當前類載入器載入。

通過上面這段程式,可以很清楚的看出擴展類載入器與啟動類載入器之間是如何實現委托模式的。

現在,我們再驗證另一個問題。我們將剛纔的Test類打成jar包,將其放置在 \lib\ext 目錄下,然後再次運行上面的代碼,結果如下:

現在,該類就不再通過 AppClassLoader 來載入,而是通過 ExtClassLoader 來載入了。如果我們試圖把jar包拷貝到\lib,嘗試通過啟動類載入器載入該類時,我們會發現編譯器無法識別該類,因為啟動類載入器除了指定目錄外,還必須是特定名稱的文件才能載入。

三、自定義類載入器

通常情況下,我們都是直接使用系統類載入器。但是,有的時候,我們也需要自定義類載入器。比如應用是通過網路來傳輸 Java 類的位元組碼,為保證安全性,這些位元組碼經過了加密處理,這時系統類載入器就無法對其進行載入,這樣則需要自定義類載入器來實現。自定義類載入器一般都是繼承自 ClassLoader 類,從上面對 loadClass 方法來分析來看,我們只需要運動康復中心重寫 findClass 方法即可。下麵我們通過一個示例來演示自定義類載入器的流程:

package com.paddx.test.classloading;

import java.io.*;

/**
 * Created by liuxp on 16/3/12.
 */
public class MyClassLoader extends ClassLoader {

    private String root;

    protected Class<?> findClass(String name) throws ClassNotFoundException {
        byte[] classData = loadClassData(name);
        if (classData == null) {
            throw new ClassNotFoundException();
        } else {
            return defineClass(name, classData, 0, classData.length);
        }
    }

    private byte[] loadClassData(String className) {
        String fileName = root + File.separatorChar
                + className.replace('.', File.separatorChar) + ".class";
        try {
            InputStream ins = new FileInputStream(fileName);
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            int bufferSize = 1024;
            byte[] buffer = new byte[bufferSize];
            int length = 0;
            while ((length = ins.read(buffer)) != -1) {
                baos.write(buffer, 0, length);
            }
            return baos.toByteArray();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

    public String getRoot() {
        return root;
    }

    public void setRoot(String root) {
        this.root = root;
    }

    public static void main(String[] args)  {

        MyClassLoader classLoader = new MyClassLoader();
        classLoader.setRoot("/Users/liuxp/tmp");

        Class<?> testClass = null;
        try {
            testClass = classLoader.loadClass("com.paddx.test.classloading.Test");
            Object object = testClass.newInstance();
            System.out.println(object.getClass().getClassLoader());
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }
}

運行上面的程式,輸出結果如下:

自定義類載入器的核心在於對位元組碼文件的獲取,如果是加密的位元組碼則需要在該類中對文件進行解密。由於這裡只是演示,我並未對class文件進行加密,因此沒有解密的過程。這裡有幾點需要註意:

1、這裡傳遞的文件名需要是類的全限定性名稱,即com.paddx.test.classloading.Test格式的,因為 defineClass 方法是按這種格式進行處理的。 
2、最好不要重寫loadClass方法,因為這樣容易破壞雙親委托模式。 
3、這類 Test 類本身可以被 AppClassLoader 類載入,因此我們不能把 com/paddx/test/classloading/Test.class 放在類路徑下。否則,由於雙親委托機制的存在,會直接導致該類由 AppClassLoader 載入,而不會通過我們自定義類載入器來載入。

四、總結

雙親委派機制能很好地解決類載入的統一性問題。對一個 Class 對象來說,如果類載入器不同,即便是同一個位元組碼文件,生成的 Class 對象也是不等的。也就是說,類載入器相當於 Class 對象的一個命名空間。雙親委派機制則保證了基類都由相同的類載入器載入,這樣就避免了同一個位元組碼文件被多次載入生成不同的 Class 對象的問題。但雙親委派機制僅僅是Java 規範所推薦的一種實現方式,它並不是強制性的要求。近年來,很多熱部署的技術都已不遵循這一規則,如 OSGi 技術就採用了一種網狀的結構,而非雙親委派機制。

 

免責聲明:本文章和信息來源於國際互聯網,本網轉載出於傳遞更多信息和學習之目的。如轉載稿涉及版權等問題,請立即聯繫。我們會予以更改或刪除相關文章,保證您的權利。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • JdbcUtils工具類3.0最終版,添加了事務相關功能和釋放鏈接。最終版本可以直接打成jar包,在後面的基本項目都會使用該工具類 1. JdbcUtils代碼 2. 在src下給出c3p0-config.xml配置文件 3. 總結 從第一個基本版本1.0到加入連接池2.0再到現在的事務,一步一個腳 ...
  • 算術異常類:ArithmeticExecption 空指針異常類:NullPointerException 類型強制轉換異常:ClassCastException 數組負下標異常:NegativeArrayException 數組下標越界異常:ArrayIndexOutOfBoundsExcepti ...
  • 在《基於Spring Boot,使用JPA操作Sql Server資料庫完成CRUD》,《基於Spring Boot,使用JPA調用Sql Server資料庫的存儲過程並返回記錄集合》完成了CRUD,調用存儲過程查詢數據。 很多複雜的情況下,會存在要直接執行SQL來獲取數據。 通過“EntityMa ...
  • 標題示例: 標題一 標題二 標題三 標題四 標題五 標題六 連接示例: " " "github" 無序列表示例: 1 2 3 4 代碼示例: 1、一行代碼用``包含 2、多行代碼用一對 { "code":1, "message":"成功", "object":{ }, "map":{}, "hand ...
  • 如果準備工作: 1.Python 2.Django 3.Git 安裝Python: 官網下載 安裝Django: 現在正式開始創建一個名為my_blog的Django項目: 建立Django app(article): 到目前為止的項目結構如下: 併在my_blog/my_blog/setting. ...
  • 1.什麼是用例? 用例模型主要應用在工程開發的初期進行系統需求分析階段,描述了系統具備什麼功能,也就是說從用戶的角度觀察系統應該支持哪些功能,同時幫助系統分析員對系統功能有個全面的認識,從巨集觀上描述系統的行為。 用例模型包括的基本元素有:用例,角色,系統。 2用例的作用 一個系統中可以包含多個用例, ...
  • 【那座山,正當頂上,有一塊仙石。其石有三丈六尺五寸高,有二丈四尺圍圓。三丈六尺五寸高,按周天三百六十五度;二丈四尺圍圓,按政歷二十四氣。上有九竅八孔,按九宮八卦。四面更無樹木遮陰,左右倒有芝蘭相襯。蓋自開闢以來,每受天真地秀,日精月華,感之既久,遂有靈通之意。內育仙胞,一日迸裂,產一石卵,似圓球樣大 ...
  • 1.什麼是UML? 面對日益複雜的軟體需求的挑戰,面向過程的開發已經不能再滿足,面向對象的開發模式應運而生,隨即出現瞭如 java ,c++等面向對象編程的語言。對於軟體的開發也有了新的思維——面向對象建模。在此理論以及實踐的基礎上,UML誕生了,其作用就是將使用面向對象模型開發軟體的思維方法,以及 ...
一周排行
    -Advertisement-
    Play Games
  • Timer是什麼 Timer 是一種用於創建定期粒度行為的機制。 與標準的 .NET System.Threading.Timer 類相似,Orleans 的 Timer 允許在一段時間後執行特定的操作,或者在特定的時間間隔內重覆執行操作。 它在分散式系統中具有重要作用,特別是在處理需要周期性執行的 ...
  • 前言 相信很多做WPF開發的小伙伴都遇到過表格類的需求,雖然現有的Grid控制項也能實現,但是使用起來的體驗感並不好,比如要實現一個Excel中的表格效果,估計你能想到的第一個方法就是套Border控制項,用這種方法你需要控制每個Border的邊框,並且在一堆Bordr中找到Grid.Row,Grid. ...
  • .NET C#程式啟動閃退,目錄導致的問題 這是第2次踩這個坑了,很小的編程細節,容易忽略,所以寫個博客,分享給大家。 1.第一次坑:是windows 系統把程式運行成服務,找不到配置文件,原因是以服務運行它的工作目錄是在C:\Windows\System32 2.本次坑:WPF桌面程式通過註冊表設 ...
  • 在分散式系統中,數據的持久化是至關重要的一環。 Orleans 7 引入了強大的持久化功能,使得在分散式環境下管理數據變得更加輕鬆和可靠。 本文將介紹什麼是 Orleans 7 的持久化,如何設置它以及相應的代碼示例。 什麼是 Orleans 7 的持久化? Orleans 7 的持久化是指將 Or ...
  • 前言 .NET Feature Management 是一個用於管理應用程式功能的庫,它可以幫助開發人員在應用程式中輕鬆地添加、移除和管理功能。使用 Feature Management,開發人員可以根據不同用戶、環境或其他條件來動態地控制應用程式中的功能。這使得開發人員可以更靈活地管理應用程式的功 ...
  • 在 WPF 應用程式中,拖放操作是實現用戶交互的重要組成部分。通過拖放操作,用戶可以輕鬆地將數據從一個位置移動到另一個位置,或者將控制項從一個容器移動到另一個容器。然而,WPF 中預設的拖放操作可能並不是那麼好用。為瞭解決這個問題,我們可以自定義一個 Panel 來實現更簡單的拖拽操作。 自定義 Pa ...
  • 在實際使用中,由於涉及到不同編程語言之間互相調用,導致C++ 中的OpenCV與C#中的OpenCvSharp 圖像數據在不同編程語言之間難以有效傳遞。在本文中我們將結合OpenCvSharp源碼實現原理,探究兩種數據之間的通信方式。 ...
  • 一、前言 這是一篇搭建許可權管理系統的系列文章。 隨著網路的發展,信息安全對應任何企業來說都越發的重要,而本系列文章將和大家一起一步一步搭建一個全新的許可權管理系統。 說明:由於搭建一個全新的項目過於繁瑣,所有作者將挑選核心代碼和核心思路進行分享。 二、技術選擇 三、開始設計 1、自主搭建vue前端和. ...
  • Csharper中的表達式樹 這節課來瞭解一下表示式樹是什麼? 在C#中,表達式樹是一種數據結構,它可以表示一些代碼塊,如Lambda表達式或查詢表達式。表達式樹使你能夠查看和操作數據,就像你可以查看和操作代碼一樣。它們通常用於創建動態查詢和解析表達式。 一、認識表達式樹 為什麼要這樣說?它和委托有 ...
  • 在使用Django等框架來操作MySQL時,實際上底層還是通過Python來操作的,首先需要安裝一個驅動程式,在Python3中,驅動程式有多種選擇,比如有pymysql以及mysqlclient等。使用pip命令安裝mysqlclient失敗應如何解決? 安裝的python版本說明 機器同時安裝了 ...