Java中關於類別載入器的程式碼詳解
這篇文章主要為大家詳細介紹了Java中類別載入器的相關資料,具有一定的參考價值,有興趣的小夥伴們可以參考一下
從java的動態性到類別載入機制
Java是一種動態語言。那麼要怎麼理解這個「動態」呢?或者說一門語言具備了什麼特性,才能稱之為動態語言呢?對於java,我是這樣理解的。
JVM(java虛擬機器)執行的不是本機機器碼指令,而是執行一種稱為字節碼的指令(存在於class檔案中)。這就要求虛擬機器在真正執行字節碼之前,先把相關的class檔案載入到記憶體中。虛擬機器不是一次載入所有需要的class文件,因為它在執行的時候根本不會知道以後會用到哪些class文件。它是每用到一個類,就會在運行時「動態地」載入和這個類相關的class檔。這就是java被稱為動態性語言的根本原因。除了動態載入類別之外,還會動態的初始化類,對類別進行動態連結。動態初始化和動態連結放在其他文章中進行介紹。本文只關心類別的載入。
在JVM中負責載入類別的正是本文要介紹的類別載入器(ClassLoader),所以,類別載入器是JVM不可或缺的重要元件。
java中的類別載入器及類別載入器運作原理
java中(指的是javase)有三種載入器。每個類別載入器在創建的時候已經指定他們對應的目錄, 也就是說每個類別載入器去哪裡載入類別是確定的,我認為在ClassLoader類別中應該會有getTargetPath()之類的方法, 得到他們對應的路徑,找了找jdk的文檔,發現是沒有的。以下是這三種類載入器和他們對應的路徑:
## * AppClassLoader -- 載入classpath指定的路徑中的類別 * ExtClassLoader -- 載入jre/lib/ext目錄下或java.ext .dirs系統屬性定義的目錄下的類別
*
BootStrap -- 載入JRE/lib/rt.jar中的類別
Name方法的實作如下:
public static Class<?> forName(String name, boolean initialize, ClassLoader loader) throws ClassNotFoundException { if (loader == null) { SecurityManager sm = System.getSecurityManager(); if (sm != null) { ClassLoader ccl = ClassLoader.getCallerClassLoader(); if (ccl != null) { sm.checkPermission( SecurityConstants.GET_CLASSLOADER_PERMISSION); } } } return forName0(name, initialize, loader); } /** Called after security checks have been made. */ private static native Class forName0(String name, boolean initialize, ClassLoader loader) throws ClassNotFoundException;
類別載入器的三個特性
#類別載入器有三個特性,分別為委派,可見性和單一性,其他文章上對這三個特性的介紹如下: * 委託機制是指將載入一個類別的請求交給父類別載入器,如果這個父類別載入器不能夠找到或載入這個類,那就再載入它。 * 可見性的原理是子類別的載入器可以看見所有的父類別載入器載入的類,而父類別載入器看不到子類別載入器載入的類別。
* 單一性原理是指只載入一個類別一次,這是由委託機制確保子類別載入器不會再次載入父類別載入器載入過的類別。
#
ClassLoader appClassLoader = ClassLoaderTest.class.getClassLoader(); System.out.println(appClassLoader); //sun.misc.Launcher$AppClassLoader@19821f ClassLoader extClassLoader = appClassLoader.getParent(); System.out.println(extClassLoader); //sun.misc.Launcher$ExtClassLoader@addbf1 //AppClassLoader的父加载器是ExtClassLoader System.out.println(extClassLoader.getParent()); //null //ExtClassLoader的父加载器是null, 也就是BootStrap,这是由c语言实现的
系统类加载器和线程上下文类加载器
在java中,还存在两个概念,分别是系统类加载器和线程上下文类加载器。
其实系统类加载器就是AppClassLoader应用程序类加载器,它两个值得是同一个加载器,以下代码可以验证:
ClassLoader appClassLoader = ClassLoaderTest.class.getClassLoader(); System.out.println(appClassLoader); //sun.misc.Launcher$AppClassLoader@19821f ClassLoader sysClassLoader = ClassLoader.getSystemClassLoader(); System.out.println(sysClassLoader); //sun.misc.Launcher$AppClassLoader@19821f //由上面的验证可知, 应用程序类加载器和系统类加载器是相同的, 因为地址是一样的
这两个类加载器对应的输出,不仅类名相同,连对象的哈希值都是一样的,这充分说明系统类加载器和应用程序类加载器不仅是同一个类,更是同一个类的同一个对象。
每个线程都会有一个上下文类加载器,由于在线程执行时加载用到的类,默认情况下是父线程的上下文类加载器, 也就是AppClassLoader。
new Thread(new Runnable() { @Override public void run() { ClassLoader threadcontextClassLosder = Thread.currentThread().getContextClassLoader(); System.out.println(threadcontextClassLosder); //sun.misc.Launcher$AppClassLoader@19821f } }).start();
这个子线程在执行时打印的信息为sun.misc.Launcher$AppClassLoader@19821f,可以看到和主线程中的AppClassLoader是同一个对象(哈希值相同)。
也可以为线程设置特定的类加载器,这样的话,线程在执行时就会使用这个特定的类加载器来加载使用到的类。如下代码:
Thread th = new Thread(new Runnable() { @Override public void run() { ClassLoader threadcontextClassLosder = Thread.currentThread().getContextClassLoader(); System.out.println(threadcontextClassLosder); //jg.zhang.java.testclassloader.ClassLoaderTest$3@1b67f74 } }); th.setContextClassLoader(new ClassLoader() {}); th.start();
在线程运行之前,为它设置了一个匿名内部类的类加载器对象,线程运行时,输出的信息为:jg.zhang.java.testclassloader.ClassLoaderTest$3@1b67f74,也就是我们设置的那个类加载器对象。
类加载器的可见性
下面验证类加载器的可见性,也就是 子类的加载器可以看见所有的父类加载器加载的类,而父类加载器看不到子类加载器加载的类。
以下代码使用父加载器ExtClassLoader加载子加载器AppClassLoader路径下的类,由输出可知,是不可能实现的。
try { Class.forName("jg.zhang.java.testConcurrent.Person", true, ClassLoaderTest.class.getClassLoader().getParent()); System.out.println("1 -- 类被加载"); } catch (ClassNotFoundException e) { //e.printStackTrace(); System.out.println("1 -- 未找到类"); }
输出为 :1 -- 未找到类 。说明抛出了ClassNotFoundException异常。原因是让ExtClassLoader加载 jg.zhang.java.testConcurrent.Person这个类因为这个类不在jre/lib/ext目录下或者java.ext.dirs系统属性定义的目录下,所以抛出ClassNotFoundException。所以父加载器不能加载应该被子加载器加载的类。也就是说这个类在父加载器中不可见。这种机制依赖于委派机制。
下面代码使用子加载器AppClassLoader 加载父加载器BootStrap中的类,这是可以实现的。
try { Class.forName("java.lang.String", true, ClassLoaderTest.class.getClassLoader()); System.out.println("2 -- 类被加载"); } catch (ClassNotFoundException e) { //e.printStackTrace(); System.out.println("2 -- 未找到类"); }
输出为:2 -- 类被加载。说明成功加载了String类。是因为在指定由AppClassLoader加载String类时,由AppClassLoader一直委派到BootStrap加载。虽然是由子加载器的父加载器加载的,但是也可以说,父加载器加载的类对于子加载器来说是可见的。这同样依赖于委派机制。其实在虚拟机启动初期,java.lang.String已经被BootStrap预加载了,这时再次加载,虚拟机发现已经加载,不会再重复加载。这同时也证明了类加载器的单一性。
测试代码
到此为止,类加载器的知识就全部讲完了。以下是整个测试代码:
package jg.zhang.java.testclassloader; /** * 参考ImportNew上的一篇文章<<类加载器的工作原理>>, * 文章地址:http://www.importnew.com/6581.html * * Java类加载器基于三个机制:委托、可见性和单一性。 * 委托机制是指将加载一个类的请求交给父类加载器,如果这个父类加载器不能够找到或者加载这个类,那么再加载它。 * 可见性的原理是子类的加载器可以看见所有的父类加载器加载的类,而父类加载器看不到子类加载器加载的类。 * 单一性原理是指仅加载一个类一次,这是由委托机制确保子类加载器不会再次加载父类加载器加载过的类。 * * 三种类加载器: 每个类加载器在创建的时候已经指定他们对应的目录, 也就是说每个类加载器去哪里加载类是确定的 * 我认为在ClassLoader类中应该会有getTargetPath()之类的方法, 得到他们对应的路径,找了找jdk的文档,发现是没有的. * AppClassLoader -- 加载classpath指定的路径中的类 * ExtClassLoader -- 加载jre/lib/ext目录下或者java.ext.dirs系统属性定义的目录下的类 * BootStrap -- 加载JRE/lib/rt.jar中的类 * * * * @author zhangjg * */ public class ClassLoaderTest { public static void main(String[] args) { test1(); test2(); test3(); } /** * 验证线程上下文类加载器 */ private static void test3() { /** * 1 每个线程都会有一个上下文类加载器,由于在线程执行时加载用到的类,默认情况下是父线程 * 的上下文类加载器, 也就是AppClassLoader */ new Thread(new Runnable() { @Override public void run() { ClassLoader threadcontextClassLosder = Thread.currentThread().getContextClassLoader(); System.out.println(threadcontextClassLosder); //sun.misc.Launcher$AppClassLoader@19821f } }).start(); /** * 2 也可以给创建的线程设定特定的上下文类加载器 */ Thread th = new Thread(new Runnable() { @Override public void run() { ClassLoader threadcontextClassLosder = Thread.currentThread().getContextClassLoader(); System.out.println(threadcontextClassLosder); //jg.zhang.java.testclassloader.ClassLoaderTest$3@1b67f74 } }); th.setContextClassLoader(new ClassLoader() {}); th.start(); } /** * 测试可见性,可见性依赖于委托机制 */ private static void test2() { /** * 1 让ExtClassLoader加载 jg.zhang.java.testConcurrent.Person这个类 * 因为这个类不在jre/lib/ext目录下或者java.ext.dirs系统属性定义的目录下 * 所以抛出ClassNotFoundException * * 所以父加载器不能加载应该被子加载器加载的类,这个类在父加载器中不可见 * 这种机制依赖于委派机制 */ try { Class.forName("jg.zhang.java.testConcurrent.Person", true, ClassLoaderTest.class.getClassLoader().getParent()); System.out.println("1 -- 类被加载"); } catch (ClassNotFoundException e) { //e.printStackTrace(); System.out.println("1 -- 未找到类"); } /** * 2 让AppClassLoader加载java.lang.String类 * 没有抛出异常,说明类被正常加载了 * 虽然是由AppClassLoader一直委派到BootStrap而加载的 * 所以可以说,父加载器加载的类对于子加载器来说是可见的,这同样依赖于委派机制 * * 其实在虚拟机启动初期,java.lang.String已经被BootStrap预加载了 * 这时再次加载,虚拟机发现已经加载,不会再重复加载 */ try { Class.forName("java.lang.String", true, ClassLoaderTest.class.getClassLoader()); System.out.println("2 -- 类被加载"); } catch (ClassNotFoundException e) { //e.printStackTrace(); System.out.println("2 -- 未找到类"); } } /** * 验证三种类加载器的父子关系 */ private static void test1() { ClassLoader appClassLoader = ClassLoaderTest.class.getClassLoader(); System.out.println(appClassLoader); //sun.misc.Launcher$AppClassLoader@19821f ClassLoader sysClassLoader = ClassLoader.getSystemClassLoader(); System.out.println(sysClassLoader); //sun.misc.Launcher$AppClassLoader@19821f //由上面的验证可知, 应用程序类加载器和系统类加载器是相同的, 因为地址是一样的 ClassLoader extClassLoader = appClassLoader.getParent(); System.out.println(extClassLoader); //sun.misc.Launcher$ExtClassLoader@addbf1 //AppClassLoader的父加载器是ExtClassLoader System.out.println(extClassLoader.getParent()); //null //ExtClassLoader的父加载器是null, 也就是BootStrap,这是由c语言实现的 } }
以上是Java中關於類別載入器的程式碼詳解的詳細內容。更多資訊請關注PHP中文網其他相關文章!

熱AI工具

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

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

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

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

熱門文章

熱工具

記事本++7.3.1
好用且免費的程式碼編輯器

SublimeText3漢化版
中文版,非常好用

禪工作室 13.0.1
強大的PHP整合開發環境

Dreamweaver CS6
視覺化網頁開發工具

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

Java 8引入了Stream API,提供了一種強大且表達力豐富的處理數據集合的方式。然而,使用Stream時,一個常見問題是:如何從forEach操作中中斷或返回? 傳統循環允許提前中斷或返回,但Stream的forEach方法並不直接支持這種方式。本文將解釋原因,並探討在Stream處理系統中實現提前終止的替代方法。 延伸閱讀: Java Stream API改進 理解Stream forEach forEach方法是一個終端操作,它對Stream中的每個元素執行一個操作。它的設計意圖是處

PHP是一種廣泛應用於服務器端的腳本語言,特別適合web開發。 1.PHP可以嵌入HTML,處理HTTP請求和響應,支持多種數據庫。 2.PHP用於生成動態網頁內容,處理表單數據,訪問數據庫等,具有強大的社區支持和開源資源。 3.PHP是解釋型語言,執行過程包括詞法分析、語法分析、編譯和執行。 4.PHP可以與MySQL結合用於用戶註冊系統等高級應用。 5.調試PHP時,可使用error_reporting()和var_dump()等函數。 6.優化PHP代碼可通過緩存機制、優化數據庫查詢和使用內置函數。 7

PHP和Python各有優勢,選擇應基於項目需求。 1.PHP適合web開發,語法簡單,執行效率高。 2.Python適用於數據科學和機器學習,語法簡潔,庫豐富。

膠囊是一種三維幾何圖形,由一個圓柱體和兩端各一個半球體組成。膠囊的體積可以通過將圓柱體的體積和兩端半球體的體積相加來計算。本教程將討論如何使用不同的方法在Java中計算給定膠囊的體積。 膠囊體積公式 膠囊體積的公式如下: 膠囊體積 = 圓柱體體積 兩個半球體體積 其中, r: 半球體的半徑。 h: 圓柱體的高度(不包括半球體)。 例子 1 輸入 半徑 = 5 單位 高度 = 10 單位 輸出 體積 = 1570.8 立方單位 解釋 使用公式計算體積: 體積 = π × r2 × h (4

PHP和Python各有優勢,適合不同場景。 1.PHP適用於web開發,提供內置web服務器和豐富函數庫。 2.Python適合數據科學和機器學習,語法簡潔且有強大標準庫。選擇時應根據項目需求決定。

PHP適合web開發,特別是在快速開發和處理動態內容方面表現出色,但不擅長數據科學和企業級應用。與Python相比,PHP在web開發中更具優勢,但在數據科學領域不如Python;與Java相比,PHP在企業級應用中表現較差,但在web開發中更靈活;與JavaScript相比,PHP在後端開發中更簡潔,但在前端開發中不如JavaScript。

Java是熱門程式語言,適合初學者和經驗豐富的開發者學習。本教學從基礎概念出發,逐步深入解說進階主題。安裝Java開發工具包後,可透過建立簡單的「Hello,World!」程式來實踐程式設計。理解程式碼後,使用命令提示字元編譯並執行程序,控制台上將輸出「Hello,World!」。學習Java開啟了程式設計之旅,隨著掌握程度加深,可創建更複雜的應用程式。
