摘要:以上文中的類(lèi)的加載過(guò)程為例,它的加載器為系統(tǒng)類(lèi)加載器。自定義加載器編寫(xiě)自定義加載器并不困難,只要繼承抽象類(lèi)并覆蓋方法就行了。源碼來(lái)自參考資料類(lèi)加載機(jī)制與類(lèi)加載器架構(gòu)深入探討類(lèi)加載器
序
我是在關(guān)于Java的面試題里了解到類(lèi)加載器的,在這之前從未想過(guò)Java里類(lèi)是如何被加載、解析的,一直以為只要Import就好了。事實(shí)上Java類(lèi)加載器是一塊非常重要的內(nèi)容,可以用在類(lèi)層次劃分、OSGi、熱部署、代碼加密等領(lǐng)域。即使業(yè)務(wù)上可能沒(méi)有涉及到,了解相關(guān)知識(shí)對(duì)排除BUG也是有幫助的。
類(lèi)加載器基本概念平時(shí)在編寫(xiě)代碼時(shí),想使用什么類(lèi)就Import就好了,好像這些類(lèi)一開(kāi)始就在JVM里了一樣,現(xiàn)在我們知道這是因?yàn)?b>JVM自動(dòng)為我們加載了這些類(lèi)。顧名思義,類(lèi)加載器的工作主要是加載Java字節(jié)碼文件(也就是.class文件)到虛擬機(jī)里,并解析為java.lang.Class類(lèi)的一個(gè)實(shí)例。到這里,被加載的類(lèi)還是不能像平時(shí)一樣直接new一個(gè)對(duì)象出來(lái)的。因?yàn)橐粋€(gè)類(lèi)總共要經(jīng)歷加載、驗(yàn)證、解析、初始化等4個(gè)步驟后才是Java里的一個(gè)類(lèi)型。后面幾個(gè)步驟不是本文重點(diǎn),大家可以自行學(xué)習(xí)。
類(lèi)加載器的組成類(lèi)加載器一共有4種,分別是引導(dǎo)類(lèi)加載器(bootstrap class loader)、擴(kuò)展類(lèi)加載器(extensions class loader)、系統(tǒng)類(lèi)加載器(system class loader)、自定義加載器,它們之間的加載關(guān)系如下圖所示:
其中,除了引導(dǎo)類(lèi)加載器是用原生代碼實(shí)現(xiàn),其余的加載器都是繼承自抽象類(lèi)java.lang.ClassLoader。而且系統(tǒng)自帶的3個(gè)加載器都有自己的特殊之處。
引導(dǎo)類(lèi)加載器引導(dǎo)類(lèi)加載器是用來(lái)加載Java的核心庫(kù),像是java.lang包等這些Java應(yīng)用必備的類(lèi)都是引導(dǎo)類(lèi)加載器加載的。加載路徑是<JAVA_HOME>lib目錄中的或者是-Xbootclasspath參數(shù)所指定的目錄中,被JVM所識(shí)別的文件(通過(guò)名字識(shí)別,名字必須是rt.jar)。因?yàn)橐龑?dǎo)類(lèi)加載器是用原生代碼實(shí)現(xiàn)的,所以不能在Java代碼中直接引用到引導(dǎo)類(lèi)加載器。
擴(kuò)展類(lèi)加載器顧名思義,擴(kuò)展類(lèi)加載器是用來(lái)加載Java的擴(kuò)展類(lèi)庫(kù)。加載路徑是<JAVA_HOME>libext目錄中的或者是java.ext.dirs系統(tǒng)變量所指定的路徑中的所有類(lèi)庫(kù)。
系統(tǒng)類(lèi)加載器系統(tǒng)類(lèi)加載器的加載路徑是Java應(yīng)用的類(lèi)路徑(CLASSPATH),也就是說(shuō)在沒(méi)有自定義加載器的情況下,Java應(yīng)用的類(lèi)都是由系統(tǒng)類(lèi)加載器加載的。而且該加載器可以用ClassLoader類(lèi)的getSystemClassLoader()方法直接獲取到。
除了引導(dǎo)類(lèi)加載器,每個(gè)加載器都有一個(gè)父加載器。比如加載器A加載了加載器B,那么加載器A就是加載器B的父加載器,可以通過(guò)java.lang.ClassLoader的getParent()方法獲取父加載器,而且Java中每個(gè)Class對(duì)象都維護(hù)著一個(gè)加載器引用,可以通過(guò)getClassLoader()方法獲取加載該類(lèi)的加載器。
例如下面這段代碼:
public class Main { public static void main(String[] args) { ClassLoader loader = Main.class.getClassLoader(); while (loader != null) { System.out.println(loader.toString()); loader = loader.getParent(); } } }
這里輸出了Main類(lèi)的加載器與其所有的父加載器,運(yùn)行結(jié)果:
sun.misc.Launcher$AppClassLoader@18b4aac2 sun.misc.Launcher$ExtClassLoader@1540e19d Process finished with exit code 0
我們看到Main類(lèi)的加載器是系統(tǒng)類(lèi)加載器,它的父加載器是擴(kuò)展類(lèi)加載器。擴(kuò)展類(lèi)加載器的父加載器應(yīng)該是引導(dǎo)類(lèi)加載器才對(duì),這里沒(méi)有輸出是因?yàn)橛行?b>JDK的實(shí)現(xiàn)里在父加載器為引導(dǎo)類(lèi)加載器的情況下是返回null的。
雙親委托模式第一次看到雙親委托模式這個(gè)詞的時(shí)候就感覺(jué)意義不明,完全不知道是什么意思。在了解了加載器的加載過(guò)程之后,才發(fā)現(xiàn)是一種代理模式。
以上文中的Main類(lèi)的加載過(guò)程為例,它的加載器為系統(tǒng)類(lèi)加載器。但是系統(tǒng)類(lèi)加載器不會(huì)直接去加載這個(gè)類(lèi),而是先委托給它的父加載器,也就是擴(kuò)展類(lèi)加載器。同樣,擴(kuò)展類(lèi)加載器也會(huì)先委托給它的父加載器,一直委托到引導(dǎo)類(lèi)加載器才開(kāi)始真正的嘗試加載,如果加載失敗就返回由發(fā)出委托的加載器嘗試加載。
這樣做的目的是為了保護(hù)Java核心庫(kù)和保持類(lèi)型安全。因?yàn)樵?b>JVM中判斷兩個(gè)類(lèi)是否相同,不僅僅是看它們的全名是否相同,還要判斷它們的加載器是否相同。通過(guò)雙親委托模式就能保證每次加載核心庫(kù)的加載器都是引導(dǎo)類(lèi)加載器,從而防止出現(xiàn)類(lèi)似于多個(gè)java.lang.Object類(lèi)型這種情況。
自定義加載器編寫(xiě)自定義加載器并不困難,只要繼承抽象類(lèi)java.lang.ClassLoader并覆蓋findClass(String name)方法就行了。不建議覆蓋 loadClass(String name)方法,因?yàn)檫@個(gè)方法里面封裝了前面提到的雙親委托模式,覆蓋可能會(huì)導(dǎo)致該模式失效。
// 源碼來(lái)自 https://www.ibm.com/developerworks/cn/java/j-lo-classloader public class FileSystemClassLoader extends ClassLoader { private String rootDir; public FileSystemClassLoader(String rootDir) { this.rootDir = rootDir; } protected Class> findClass(String name) throws ClassNotFoundException { byte[] classData = getClassData(name); if (classData == null) { throw new ClassNotFoundException(); } else { return defineClass(name, classData, 0, classData.length); } } private byte[] getClassData(String className) { String path = classNameToPath(className); try { InputStream ins = new FileInputStream(path); ByteArrayOutputStream baos = new ByteArrayOutputStream(); int bufferSize = 4096; byte[] buffer = new byte[bufferSize]; int bytesNumRead = 0; while ((bytesNumRead = ins.read(buffer)) != -1) { baos.write(buffer, 0, bytesNumRead); } return baos.toByteArray(); } catch (IOException e) { e.printStackTrace(); } return null; } private String classNameToPath(String className) { return rootDir + File.separatorChar + className.replace(".", File.separatorChar) + ".class"; } }參考資料
Java類(lèi)加載機(jī)制與Tomcat類(lèi)加載器架構(gòu)
深入探討 Java 類(lèi)加載器
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/76722.html
摘要:如果需要支持類(lèi)的動(dòng)態(tài)加載或需要對(duì)編譯后的字節(jié)碼文件進(jìn)行解密操作等,就需要與類(lèi)加載器打交道了。雙親委派模型,雙親委派模型,約定類(lèi)加載器的加載機(jī)制。任何之類(lèi)的字節(jié)碼都無(wú)法調(diào)用方法,因?yàn)樵摲椒ㄖ荒茉陬?lèi)加載的過(guò)程中由調(diào)用。 jvm系列 垃圾回收基礎(chǔ) JVM的編譯策略 GC的三大基礎(chǔ)算法 GC的三大高級(jí)算法 GC策略的評(píng)價(jià)指標(biāo) JVM信息查看 GC通用日志解讀 jvm的card table數(shù)據(jù)...
摘要:最終形成可以被虛擬機(jī)最直接使用的類(lèi)型的過(guò)程就是虛擬機(jī)的類(lèi)加載機(jī)制。即重寫(xiě)一個(gè)類(lèi)加載器的方法驗(yàn)證驗(yàn)證是連接階段的第一步,這一階段的目的是為了確保文件的字節(jié)流中包含的信息符合當(dāng)前虛擬機(jī)的要求,并且不會(huì)危害虛擬機(jī)自身的安全。 《深入理解Java虛擬機(jī):JVM高級(jí)特性與最佳實(shí)踐(第二版》讀書(shū)筆記與常見(jiàn)相關(guān)面試題總結(jié) 本節(jié)常見(jiàn)面試題(推薦帶著問(wèn)題閱讀,問(wèn)題答案在文中都有提到): 簡(jiǎn)單說(shuō)說(shuō)類(lèi)加載過(guò)...
摘要:看到的只是,而由泛型附加的類(lèi)型信息對(duì)來(lái)說(shuō)是不可見(jiàn)的。然后再加載執(zhí)行類(lèi)的靜態(tài)變量以及靜態(tài)語(yǔ)句塊。接口中基本數(shù)據(jù)類(lèi)型為而抽類(lèi)象不是的。本地方法接口主要是調(diào)用或?qū)崿F(xiàn)的本地方法及返回結(jié)果。用戶(hù)自定義類(lèi)加載器,在程序運(yùn)行期間,通過(guò)的子類(lèi)動(dòng)態(tài)加載。 編譯機(jī)制 編譯主要是把?.Java文件轉(zhuǎn)換為 .class 文件。其中轉(zhuǎn)換后的 .class 文件就包含了元數(shù)據(jù),方法信息等一些信息。比如說(shuō)元數(shù)據(jù)就...
摘要:如問(wèn)到是否使用某框架,實(shí)際是是問(wèn)該框架的使用場(chǎng)景,有什么特點(diǎn),和同類(lèi)可框架對(duì)比一系列的問(wèn)題。這兩個(gè)方向的區(qū)分點(diǎn)在于工作方向的側(cè)重點(diǎn)不同。 [TOC] 這是一份來(lái)自嗶哩嗶哩的Java面試Java面試 32個(gè)核心必考點(diǎn)完全解析(完) 課程預(yù)習(xí) 1.1 課程內(nèi)容分為三個(gè)模塊 基礎(chǔ)模塊: 技術(shù)崗位與面試 計(jì)算機(jī)基礎(chǔ) JVM原理 多線(xiàn)程 設(shè)計(jì)模式 數(shù)據(jù)結(jié)構(gòu)與算法 應(yīng)用模塊: 常用工具集 ...
摘要:加載階段在類(lèi)的加載階段,虛擬機(jī)需要完成以下件事情通過(guò)一個(gè)類(lèi)的全限定名來(lái)獲取定義此類(lèi)的二進(jìn)制字節(jié)流。驗(yàn)證階段驗(yàn)證是連接階段的第一步,這一階段的目的是為了確保文件的字節(jié)流中包含的信息符合當(dāng)前虛擬機(jī)的要求,并且不會(huì)危害虛擬機(jī)自身的安全。 注:本篇文章中的內(nèi)容是根據(jù)《深入理解Java虛擬機(jī)--JVM高級(jí)特性與最佳實(shí)踐》而總結(jié)的,如有理解錯(cuò)誤,歡迎大家指正! 虛擬機(jī)把描述類(lèi)的數(shù)據(jù)從Class文件...
閱讀 1863·2021-11-25 09:43
閱讀 1378·2021-11-22 15:08
閱讀 3802·2021-11-22 09:34
閱讀 3261·2021-09-04 16:40
閱讀 3210·2021-09-04 16:40
閱讀 573·2019-08-30 15:54
閱讀 1362·2019-08-29 17:19
閱讀 1790·2019-08-28 18:13