摘要:本文已收錄修煉內功躍遷之路在誕生之初便提出,各提供商發布很多不同平臺的虛擬機,這些虛擬機都可以載入并執行同平臺無關的字節碼。設計者在第一版虛擬機規范中便承諾,時至今日,商業機構和開源機構已在之外發展出一大批可以在上運行的語言,如等。
本文已收錄【修煉內功】躍遷之路
Java在誕生之初便提出 "Write Once, Run Anywhere",各提供商發布很多不同平臺的虛擬機,這些虛擬機都可以載入并執行同平臺無關的字節碼。
設計者在第一版Java虛擬機規范中便承諾 "In the future, we will consider bounded extensions to the Java virtual machine to provide better support for other languages",時至今日,商業機構和開源機構已在Java之外發展出一大批可以在JVM上運行的語言,如Clojure、Groovy、JRuby、Jpython、Scala、Kotlin等。這些語言都不是本地可執行程序,需要JVM將編譯后的class文件加載后才能夠運行,負責加載class文件的組件便是ClassLoader.
JVM類加載流程java語言系統內置了眾多類加載器,從一定程度上講,只存在兩種不同的類加載器:一種是啟動類加載器,此類加載由C++實現,是JVM的一部分;另一種就是所有其他的類加載器,這些類加載器均由java實現,且全部繼承自java.lang.ClassLoader
Bootstrap ClassLoader 啟動類加載器,最頂層的加載類,由C++實現,負責加載%JAVA_HOME%/lib目錄中或-Xbootclasspath中參數指定的路徑中的,并且是虛擬機識別的(按名稱)類庫
Extention ClassLoader 擴展類加載器,由啟動類加載器加載,實現為sun.misc.Launcher$ExtClassLoader,負責加載目錄%JRE_HOME%/lib/ext目錄中或-Djava.ext.dirs中參數指定的路徑中的jar包和class文件
Application ClassLoader 應用類加載器,也稱為系統類加載器(System ClassLoader,可由java.lang.ClassLoader.getSystemClassLoader()獲取),實現為sun.misc.Launcher$AppClassLoader,由啟動類加載器加載,負責加載當前應用classpath下的所有類
雙親委派模型java語言系統有眾多類加載器,包括用戶自定義類加載器,各加載器之間的加載順序如何?首先從JVM入口應用sun.misc.Launcher聊起
Launcherpublic Launcher() { ExtClassLoader localExtClassLoader; try { // 加載擴展類加載器 localExtClassLoader = ExtClassLoader.getExtClassLoader(); } catch (IOException localIOException1) { throw new InternalError("Could not create extension class loader", localIOException1); } try { // 加載應用類加載器 this.loader = AppClassLoader.getAppClassLoader(localExtClassLoader); } catch (IOException localIOException2) { throw new InternalError("Could not create application class loader", localIOException2); } // 設置AppClassLoader為線程上下文類加載器 Thread.currentThread().setContextClassLoader(this.loader); // ... static class ExtClassLoader extends java.net.URLClassLoader static class AppClassLoader extends java.net.URLClassLoader }
Launcher初始化了ExtClassLoader和AppClassLoader,并將AppClassLoader設置為線程上下文類加載器,同時,初始化AppClassLoader時傳入了ExtClassLoader實例,WHY? 這里要寫一個大大的問號
ExtClassLoader和AppClassLoader都繼承自URLClassLoader,而最終的父類則為ClassLoader。
查看源碼可以得知,初始化AppClassLoader時傳入的ExtClassLoader實例最終設置為了AppClassLoader(ClassLoader)的parent屬性,parent屬性的作用是什么?
父類加載器每個類都對應一個加載它的類加載器,我們可以通過程序來驗證
public class ClassLoaderDemo { public static void main(String[] args) { System.out.println("ClassLodarDemo"s ClassLoader is " + ClassLoaderDemo.class.getClassLoader()); System.out.println("DNSNameService"s ClassLoader is " + DNSNameService.class.getClassLoader()); System.out.println("String"s ClassLoader is " + String.class.getClassLoader()); } }
輸出為
ClassLodarDemo"s ClassLoader is sun.misc.Launcher$AppClassLoader@135fbaa4 DNSNameService"s ClassLoader is sun.misc.Launcher$ExtClassLoader@6e0be858 String"s ClassLoader is null
ClassLodarDemo為我們自己創建的類,其類加載器為AppClassLoader
DNSNameService為%JRE_HOME%/lib/ext目錄下的類,其類加載器為ExtClassLoader
String存在于rt.jar中,但其類加載器為null,這里是應為rt.jar由Bootstrap ClassLoader加載,而Bootstrap ClassLoader是由C++編寫,屬于JVM的一部分
每個類加載器,都有一個父類加載器(parent),同樣通過程序驗證
public class ClassLoaderDemo { public static void main(String[] args) { System.out.println("ClassLodarDemo"s ClassLoader is " + ClassLoaderDemo.class.getClassLoader()); System.out.println("The Parent of ClassLodarDemo"s ClassLoader is " + ClassLoaderDemo.class.getClassLoader().getParent()); System.out.println("The GrandParent of ClassLodarDemo"s ClassLoader is " + ClassLoaderDemo.class.getClassLoader().getParent().getParent()); } }
輸出為
ClassLodarDemo"s ClassLoader is sun.misc.Launcher$AppClassLoader@135fbaa4 The Parent of ClassLodarDemo"s ClassLoader is sun.misc.Launcher$ExtClassLoader@2503dbd3 The GrandParent of ClassLodarDemo"s ClassLoader is null
AppClassLoader的父類加載器為ExtClassLoader
ExtClassLoader的父類加載器為null,null并不代表ExtClassLoader沒有父類加載器,而是Bootstrap ClassLoader
類加載器在加載類或者其他資源時,使用的是如上圖所示的雙親委派模型,這種模型要求除了頂層的BootStrap ClassLoader外,其余的類加載器都應當有自己的父類加載器(父類加載器不是父類繼承),如果一個類加載器收到了類加載請求,首先會把這個請求委派給父類加載器加載,只有父類加載器無法完成類加載請求時,子類加載器才會嘗試自己去加載。
要理解雙親委派,可以查看ClassLoader.loadClass方法
protected Class> loadClass(String name, boolean resolve) throws ClassNotFoundException { synchronized (getClassLoadingLock(name)) { // 檢查是否已經加載過 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) { // 如果父類加載器無法加載,才嘗試加載 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; } }破壞雙親委派模型
雙親委派模型并不是一個強制性的約束模型,在java的世界中大部分的類加載器都遵循這個模型,但也有例外,一個典型的例子便是JNDI服務。
JNDI存放于rt.jar,由啟動類加載器加載,JNDI的目的是對資源進行集中管理和查找,它需要調用由各廠商實現并部署在應用程序ClassPath下的JNDI接口實現的代碼,如此一來,在雙親委派模型下,啟動類加載器根本無法加載這些代碼
針對此問題,java設計團隊引入了線程上下文類加載器(Thread Context ClassLoader),這個類加載器可以通過Thread類的setContextClassLoader進行設置,默認繼承父線程類加載器
通過線程上下文類加載器,JNDI服務通過此類加載器,由父類加載器請求子類加載器完成類加載動作
破壞雙親委派模型的例子還有很多,如tomcat服務、osgi、jigsaw等等,是否破壞雙親委派模型并沒有對與錯,只是不同場景下的具體應用而已
自定義ClassLoader不論是AppClassLoader還是ExtClassLoader還是啟動類加載器,其加載類的路徑都是固定的,如果我們需要加載外部類或者資源,如某路徑下或網絡上,這樣便需要自定義類加載器
自定義類加載器,只需要繼承ClassLoader類,復寫findClass方法,在findClass方法中調用defineClass方法即可
一個ClassLoader創建時如果沒有指定parent,那么它的parent默認就是AppClassLoader
如果需要制定一個ClassLoader的父類加載器為啟動類加載器,只需要將其parent指定為null即可
首先,編寫一個測試用的類文件
public class BeLoadedClass { public void say() { System.out.println("I"m Loaded by " + this.getClass().getClassLoader()); } }
將其編譯,放入/data/classloader目錄下
接下來,編寫DiskClassLoader
public class DiskClassLoader extends URLClassLoader { public DiskClassLoader(URL path) throws MalformedURLException { super(new URL[]{path}); } public DiskClassLoader(URL path, ClassLoader parent) throws MalformedURLException { super(new URL[]{path}, parent); } }
這里直接繼承URLClassLoader類,該類findClass的實現如下
protected Class> findClass(final String name) throws ClassNotFoundException { final Class> result; try { result = AccessController.doPrivileged( new PrivilegedExceptionAction>() { public Class> run() throws ClassNotFoundException { // 類文件全路徑 String path = name.replace(".", "/").concat(".class"); // 指定資源目錄下查找 Resource res = ucp.getResource(path, false); if (res != null) { try { // 調用defineClass生成類 return defineClass(name, res); } catch (IOException e) { throw new ClassNotFoundException(name, e); } } else { return null; } } }, acc); } catch (java.security.PrivilegedActionException pae) { throw (ClassNotFoundException) pae.getException(); } if (result == null) { throw new ClassNotFoundException(name); } return result; }
編寫測試程序
public class ClassLoaderDemo { public static void main(String[] args) throws Exception { URL path = new File("/data/classloader").toURI().toURL(); DiskClassLoader diskClassLoaderA = new DiskClassLoader(path); Class> clazzA = diskClassLoaderA.loadClass("BeLoadedClass"); Method sayA = clazzA.getMethod("say"); Object instanceA = clazzA.newInstance(); sayA.invoke(instanceA); System.out.println(diskClassLoaderA); System.out.println("clazzA@" + clazzA.hashCode()); System.out.println("===="); DiskClassLoader diskClassLoaderB = new DiskClassLoader(path, diskClassLoaderA); Class> clazzB = diskClassLoaderB.loadClass("BeLoadedClass"); Method sayB = clazzB.getMethod("say"); Object instanceB = clazzA.newInstance(); sayB.invoke(instanceB); System.out.println(diskClassLoaderB); System.out.println("clazzB@" + clazzB.hashCode()); System.out.println("===="); DiskClassLoader diskClassLoaderC = new DiskClassLoader(path); Class> clazzC = diskClassLoaderC.loadClass("BeLoadedClass"); Method sayC = clazzC.getMethod("say"); Object instanceC = clazzC.newInstance(); sayC.invoke(instanceC); System.out.println(diskClassLoaderC); System.out.println("clazzC@" + clazzC.hashCode()); System.out.println("===="); System.out.println("clazzA == clazzB " + (clazzA == clazzB)); System.out.println("clazzC == clazzB " + (clazzC == clazzB)); } }
輸出為
I"m Loaded by com.manerfan.jvm.oom.DiskClassLoader@4b67cf4d com.manerfan.jvm.DiskClassLoader@4b67cf4d clazzA@312714112 ==== I"m Loaded by com.manerfan.jvm.oom.DiskClassLoader@4b67cf4d com.manerfan.jvm.DiskClassLoader@29453f44 clazzB@312714112 ==== I"m Loaded by com.manerfan.jvm.oom.DiskClassLoader@5cad8086 com.manerfan.jvm.DiskClassLoader@5cad8086 clazzC@1639705018 ==== clazzA == clazzB true clazzC == clazzB false
這里我們定義了3個ClassLoader,類搜索路徑均為/data/classloader,其中diskClassLoaderB的父類加載器為diskClassLoaderA
clazzA clazzB clazzC 分別由 diskClassLoaderA diskClassLoaderB diskClassLoaderC 加載
instanceA instanceB instanceA 分別由 clazzA clazzB clazzC 創建
從輸出可以看出,instanceA及instanceB所對應的class均由diskClassLoaderA加載
雖然diskClassLoaderA與diskClassLoaderB為兩個不同的類加載器,但由于diskClassLoaderB的父類加載器為diskClassLoaderA,從輸出結果可以看出,clazzA與clazzB完全相同(包括內存地址),這也說明clazzA與clazzB均由同一類加載器加載
而instanceC所對應的class,classC卻是由diskClassLoaderC加載
這個例子,能更好的幫助理解雙親委派模型
類的唯一性對于任意一個類,都需要由加載它的類加載器和這個類本身一同確立其在Java虛擬機中的唯一性,每一個類加載器,都擁有一個獨立的類名稱空間
如上例,雖然classA(classB)與classC的類路徑相同,但由于被不同的類加載器加載,其卻屬于兩個不同的類名稱空間
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/68622.html
摘要:本文已收錄修煉內功躍遷之路在淺談虛擬機內存模型一文中有簡單介紹過,虛擬機棧是線程私有的,每個方法在執行的同時都會創建一個棧幀,方法執行時棧幀入棧,方法結束時棧幀出棧,虛擬機中棧幀的入棧順序就是方法的調用順序寫了很多文字,但都不盡如意,十分慚 本文已收錄【修煉內功】躍遷之路 showImg(https://segmentfault.com/img/bVbtSi5?w=1654&h=96...
摘要:本文已收錄修煉內功躍遷之路學習語言的時候,需要在不同的目標操作系統上或者使用交叉編譯環境,使用正確的指令集編譯成對應操作系統可運行的執行文件,才可以在相應的系統上運行,如果使用操作系統差異性的庫或者接口,還需要針對不同的系統做不同的處理宏的 本文已收錄【修煉內功】躍遷之路 showImg(https://segmentfault.com/img/bVbtpPd?w=2065&h=11...
摘要:也正是因此,一旦出現內存泄漏或溢出問題,如果不了解的內存管理原理,那么將會對問題的排查帶來極大的困難。 本文已收錄【修煉內功】躍遷之路 showImg(https://segmentfault.com/img/bVbsP9I?w=1024&h=580); 不論做技術還是做業務,對于Java開發人員來講,理解JVM各種原理的重要性不必再多言 對于C/C++而言,可以輕易地操作任意地址的...
摘要:本文已收錄修煉內功躍遷之路初次接觸的時候感覺表達式很神奇表達式帶來的編程新思路,但又總感覺它就是匿名類或者內部類的語法糖而已,只是語法上更為簡潔罷了,如同以下的代碼匿名類內部類編譯后會產生三個文件雖然從使用效果來看,與匿名類或者內部類有相 本文已收錄【修煉內功】躍遷之路 showImg(https://segmentfault.com/img/bVbui4o?w=800&h=600)...
摘要:本文已收錄修煉內功躍遷之路我們寫的方法在被編譯為文件后是如何被虛擬機執行的對于重寫或者重載的方法,是在編譯階段就確定具體方法的么如果不是,虛擬機在運行時又是如何確定具體方法的方法調用不等于方法執行,一切方法調用在文件中都只是常量池中的符號引 本文已收錄【修煉內功】躍遷之路 showImg(https://segmentfault.com/img/bVbuesq?w=2114&h=12...
閱讀 1317·2021-10-27 14:14
閱讀 3573·2021-09-29 09:34
閱讀 2476·2019-08-30 15:44
閱讀 1715·2019-08-29 17:13
閱讀 2569·2019-08-29 13:07
閱讀 866·2019-08-26 18:26
閱讀 3341·2019-08-26 13:44
閱讀 3209·2019-08-26 13:37