Java 類加載機制詳解

2019獨角獸企業重金招聘Python工程師標準>>> hot3.png

一、類加載器

  類加載器(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的一部分,其作用是加載 <Java_Runtime_Home>/lib 目錄中的文件,并且該類加載器只加載特定名稱的文件(如 rt.jar),而不是該目錄下所有的文件。另外兩種是 Java 語言自身實現的類加載器,包括擴展類加載器(ExtClassLoader)和應用類加載器(AppClassLoader),擴展類加載器負責加載<Java_Runtime_Home>\lib\ext目錄中或系統變量 java.ext.dirs 所指定的目錄中的文件。應用程序類加載器負責加載用戶類路徑中的文件。用戶可以直接使用擴展類加載器或系統類加載器來加載自己的類,但是用戶無法直接使用啟動類加載器,除了這兩種類加載器以外,用戶也可以自定義類加載器,加載流程如下圖所示:

29143704_8IpY.png

我們可以通過一段程序來驗證這個過程:

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

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

29143704_82ad.png

從結果我們可以看出,默認情況下,用戶自定義的類使用 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?loadedClass<?>?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?statssun.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包,將其放置在?<Java_Runtime_Home>\lib\ext 目錄下,然后再次運行上面的代碼,結果如下:

29143704_yKUM.png

現在,該類就不再通過 AppClassLoader 來加載,而是通過 ExtClassLoader 來加載了。如果我們試圖把jar包拷貝到<Java_Runtime_Home>\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();}}
}

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

29143705_IsuK.png

自定義類加載器的核心在于對字節碼文件的獲取,如果是加密的字節碼則需要在該類中對文件進行解密。由于這里只是演示,我并未對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 技術就采用了一種網狀的結構,而非雙親委派機制。



轉載于:https://my.oschina.net/freelili/blog/668114

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/458717.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/458717.shtml
英文地址,請注明出處:http://en.pswp.cn/news/458717.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

JAVA vo pojo javabean dto區別

JavaBean 是一種JAVA語言寫成的可重用組件。為寫成JavaBean&#xff0c;類必須是具體的和公共的&#xff0c;并且具有無參數的構造器。JavaBean 通過提供符合一致性設計模式的公共方法將內部域暴露成員屬性。眾所周知&#xff0c;屬性名稱符合這種模式&#xff0c;其他Java 類可…

編寫的windows程序,崩潰時產生crash dump文件的辦法

一、引言 dump文件是C程序發生異常時&#xff0c;保存當時程序運行狀態的文件&#xff0c;是調試異常程序重要的方法&#xff0c;所以程序崩潰時&#xff0c;除了日志文件&#xff0c;dump文件便成了我們查找錯誤的最后一根救命的稻草。windows程序產生dump文件和linux程序產生…

Nginx+PHP實時生成不同尺寸圖片

原來圖片服務器采用Windows .net架構&#xff0c;鑒于需求需要生成各種尺寸圖片。流程說明:用戶從Nginx請求對應的圖片,判斷是否存在_200x300的對應參數&#xff0c;如果沒有就直接請求到對應目錄的原圖&#xff0c;否則繼續判斷是否在本地已經生成了對應的緩存圖片&#xff0c…

JavaScript設計模式 Item 2 -- 接口的實現

1、接口概述 1。什么是接口&#xff1f; 接口是提供了一種用以說明一個對象應該具有哪些方法的手段。盡管它可以表明這些方法的語義&#xff0c;但它并不規定這些方法應該如何實現。 2. 接口之利 促進代碼的重用。 接口可以告訴程序員一個類實現了哪些方法&#xff0c;從而幫助…

Spring Boot 樂觀鎖加鎖失敗 - 集成AOP

Spring Boot with AOP 手頭上的項目使用了Spring Boot&#xff0c; 在高并發的情況下&#xff0c;經常出現樂觀鎖加鎖失敗的情況&#xff08;OptimisticLockingFailureException&#xff0c;同一時間有多個線程在更新同一條數據&#xff09;。為了減少直接向服務使用者直接返回…

掌握VS2010調試 -- 入門指南

1 導言 在軟件開發周期中&#xff0c;測試和修正缺陷&#xff08;defect&#xff0c;defect與bug的區別&#xff1a;Bug是缺陷的一種表現形式&#xff0c;而一個缺陷是可以引起多種Bug的&#xff09;的時間遠多于寫代碼的時間。通常&#xff0c;debug是指發現缺陷并改正的過程。…

151031

create or replace procedure pr_test1 is v_case number(3): 100; beginif 2>1 thendbms_output.put_line(成立);elsif 4>3 thenif 7>6 thendbms_output.put_line(不成立);end if; elsif 6>5 thendbms_output.put_line(也行);elsedbms_output.put_line(也不成立);…

postgresql9.5 run 文件linux安裝后配置成開機服務

網上出現的比較多安裝方法要么是源碼安裝&#xff0c;要么是yum安裝&#xff0c;我發覺都要配置很多屬性&#xff0c;比較麻煩&#xff0c;所以現在我在centos7長用 run文件來安裝 http://get.enterprisedb.com/postgresql/postgresql-9.5.1-1-linux-x64.run 這里的安裝shell整…

Windows API GetProcAddress 及demo code

GetProcAddress函數檢索指定的動態鏈接庫(DLL)中的輸出庫函數地址。 函數原型&#xff1a; FARPROC GetProcAddress( HMODULE hModule, // DLL模塊句柄 LPCSTR lpProcName// 函數名 ); 參數&#xff1a; hModule [in] 包含此函數的DLL模塊的句柄。LoadLibrary、AfxLoadLibrary …

【操作系統】進程管理

進程管理 進程的基本概念 程序的順序執行及其特征 程序的順序執行:僅當前一操作(程序段)執行完后&#xff0c;才能執行后續操作。 程序順序執行時的特征&#xff1a;順序性&#xff0c;封閉性&#xff0c;可再見性。 前趨圖 前趨圖(Precedence Graph)是一個有向無循環圖&#…

va_list va_start va_end的使用

<pre name"code" class"cpp" style"color: rgb(51, 51, 51); white-space: pre-wrap; word-wrap: break-word;"><strong>一、 從printf()開始</strong> 從大家都很熟悉的格式化字符串函數開始介紹可變參數函數。 原型&#xf…

Linux學習之CentOS(三)----將Cent0S 7的網卡名稱eno16777736改為eth0

【正文】 Linux系統版本&#xff1a;CentOS_7&#xff08;64位&#xff09; 一、前言&#xff1a; 今天又從Centos 6.5裝回了Centos 7&#xff0c;畢竟還是要順應潮流嘛。安裝完成之后&#xff0c;發現發現CentOS 7默認的網卡名稱是eno16777736&#xff0c;如圖所示&#xff1a…

本地音頻播放,使用AVFoundation.framework中的AVAudioPlayer來實現

本地音頻播放,使用AVfoundation.framework中的AVAudioPlayer來實現 /*AVAudioPlayer的使用比較簡單: 1、初始化AVAudioPlayer對象&#xff0c;此時通常指定本地文件路徑 2、設置播放器屬性&#xff0c;例如重復次數、音量大小等 3、調用play方法播放。 */

AngularJS操作DOM——angular.element

addClass()-為每個匹配的元素添加指定的樣式類名after()-在匹配元素集合中的每個元素后面插入參數所指定的內容&#xff0c;作為其兄弟節點append()-在每個匹配元素里面的末尾處插入參數內容attr() - 獲取匹配的元素集合中的第一個元素的屬性的值bind() - 為一個元素綁定一個事…

C++中operator的主要用法

1&#xff0e; operator 用于類型轉換函數&#xff1a; 類型轉換函數的特征&#xff1a; 1&#xff09; 型轉換函數定義在源類中&#xff1b; 2&#xff09; 須由 operator 修飾&#xff0c;函數名稱是目標類型名或目標類名&#xff1b; 3&#xff09; 函數沒有參數&#x…

聲紋識別

一、 聲紋識別是一項根據語音波形中反映說話人生理和行為特征的語音參數&#xff0c;自動識別說話人身份的技術。與語音識別不同的是&#xff0c;聲紋識別利用的是語音信號中的說話人身份信息&#xff0c;而不考慮語音中的字詞意思。由于每個人的生物特征具有與其他人不同的唯一…

Asp.net mvc 實時生成縮率圖到硬盤

之前對于縮率圖的處理是在圖片上傳到服務器之后&#xff0c;同步生成兩張不同尺寸的縮率供前端調用&#xff0c;剛開始還能滿足需求&#xff0c;慢慢的隨著前端展示的多樣化&#xff0c;縮率圖已不能前端展示的需求&#xff0c;所以考慮做一個實時生成圖片縮率圖服務。 每次調用…

數據庫事務的隔離機制

數據庫事務(Database Transaction) &#xff0c;是指作為單個邏輯工作單元執行的一系列操作&#xff0c;要么完全地執行&#xff0c;要么完全地不執行。----百度百科就是說你定義一組數據庫操作&#xff0c;然后告訴數據庫說這些操作要么都成功&#xff0c;要么都不成功。類似于…

如何使用CppUnit進行單元測試

http://www.vckbase.com/document/viewdoc/?id1762 一、前言 測試驅動開發(TDD)是以測試作為開發過程的中心&#xff0c;它堅持&#xff0c;在編寫實際代碼之前&#xff0c;先寫好基于產品代碼的測試代碼。開發過程的目標就是首先使測試能夠通過&#xff0c;然后再優化設計結構…

錄制wav格式的音頻

項目中有面部認證、聲紋認證&#xff0c;服務器端要求上傳wav格式的音頻&#xff0c;所以寫了這樣一個小demo。 剛剛開始寫博客還不知道怎么上傳代碼&#xff0c;就復制了&#xff0c;嘻嘻 DotimeManage.h class DotimeManage; protocol DotimeManageDelegate <NSObject&g…