摘要:比如我們要加載類,無論我們用哪個類加載器去加載類,這個加載請求最終都會委托給,這樣就保證了所有加載器加載的類都是同一個類。如果沒有雙親委派模型,那就亂了套了,完全可能搞出多個不同的類。
前言
雙親委派模型是Java加載類的機制.采用雙親委派模型的好處是Java類隨著它的類加載器一起具備了一種帶有優先級的層級關系,通過這種層級關系可以避免類的重復加載.
1. 模型基礎Bootstrap ClassLoader(啟動類加載器): 負責將%JAVA_HOME%/lib目錄中或-Xbootclasspath中參數指定的路徑中的,并且是虛擬機識別的(按名稱)類庫加載到JVM中
Extension ClassLoader(擴展類加載器): 負責加載%JAVA_HOME%/lib/ext中的所有類庫
Application ClassLoader(應用程序加載器): 負責ClassPath中的類庫
2. 為什么使用雙親委派模型?1.雙親委派模型最大的好處就是讓Java類同其類加載器一起具備了一種帶優先級的層次關系。這句話可能不好理解,我們舉個例子。比如我們要加載java.lang.Object類,無論我們用哪個類加載器去加載Object類,這個加載請求最終都會委托給Bootstrap ClassLoader,這樣就保證了所有加載器加載的Object類都是同一個類。如果沒有雙親委派模型,那就亂了套了,完全可能搞出多個不同的Object類。
2.自上而下每個類加載器都會盡力加載.
1.首先加載類調用的loadClass方法,我們找到ClassLoader的loadClass():
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; } }
首先判斷了該類是否已加載.
若沒加載,則傳給雙親加載器去加載,
若雙親加載器沒能成功加載它,則自己用findClass()去加載.所以是個向上遞歸的過程.
自定義加載器時,需要重寫findClass方法,因為是空的,沒有任何內容:
protected Class> findClass(String name) throws ClassNotFoundException { throw new ClassNotFoundException(name); }4. 自己動手,編寫一個自己的類加載器
1.首先需要一個編譯好的class文件,筆者用了一個之前寫的斐波那契的類Fib.class(所在路徑:C:/Users/Think/crabapple),下面是用idea通過反編譯方式打開的class文件,注意記下class文件的包名,在后續代碼中需要使用類的全限定名稱.
// // Source code recreated from a .class file by IntelliJ IDEA // (powered by Fernflower decompiler) // package crabapple; public class Fib { public static int fib(int num) { return num < 2 ? num : fib(num - 2) + fib(num - 1); } }
2.繼承ClassLoader,重寫findClass方法:
class MyClassLoader extends ClassLoader { private String classPath; // 保存的地址 /** * 傳入地址構造函數 * @param classPath */ public MyClassLoader(String classPath) { this.classPath = classPath; } /** * 讀取class文件 * @param name * @return * @throws Exception */ private byte[] loadByte(String name) throws Exception { String inPath = classPath + "/" + name + ".class"; FileInputStream fis = new FileInputStream(inPath); int len = fis.available(); byte[] data = new byte[len]; fis.read(data); fis.close(); return data; } /** * 重寫findClass方法,讓加載的時候調用findClass方法 * @param name * @return * @throws ClassNotFoundException */ protected Class> findClass(String name) throws ClassNotFoundException { try { byte[] data = loadByte(name); // 將字節碼載入內存 return defineClass(name, data, 0, data.length); } catch (Exception e) { e.printStackTrace(); } return null; } }
loadByte方法僅用作讀取文件
findClass方法才是加載類到內存的,注意name必須填全限定名,比如java.lang.Object.
3.測試,一下將使用一些反射機制和class類的方法.
public class ClassLoaderTest extends ClassLoader { //main函數本該拋出異常有 ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException,為了好看,簡寫成Exception public static void main(String[] args) throws Exception { //初始化類加載器 MyClassLoader myClassLoader=new MyClassLoader("C:/Users/Think/crabapple"); //加載Fib類,筆者class文件包名為crabapple Class myClass=myClassLoader.loadClass("crabapple.Fib"); //獲取加載類的實例 Object object=myClass.newInstance(); //獲取該類一個名為fib,且參數為int的方法 Method method=myClass.getMethod("fib",int.class); //執行這個方法 int result=method.invoke(object,4); //打印結果 System.out.print(result); //output /** * 3 * Process finished with exit code 0 */ } }
執行成功
我們來分析下,Fib類的加載過程,初始化自定義類加載器后,loadClass方法肯定將其委派到雙親Application ClassLoader,而Application ClassLoader又將其委派到Extension ClassLoader,繼而委派到Bootstrap ClassLoader.但是Bootstrap ClassLoader發現Fib并不在自己的加載能力范圍內,于是移向Extension ClassLoader,同理Extension ClassLoader只能加載/ext中的class,繼而讓給Application ClassLoader,而Application ClassLoader只加載classpath中的類,于是又回到我們自定義的MyClassLoader,幸好我們重寫了findClass方法進而執行了加載,否在findClass拋出找不到類的異常.至此Fib類加載完成.
結語如上便是筆者對雙親委派模型的總結,如有錯誤,歡迎指正.
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/73936.html
摘要:最終形成可以被虛擬機最直接使用的類型的過程就是虛擬機的類加載機制。即重寫一個類加載器的方法驗證驗證是連接階段的第一步,這一階段的目的是為了確保文件的字節流中包含的信息符合當前虛擬機的要求,并且不會危害虛擬機自身的安全。 《深入理解Java虛擬機:JVM高級特性與最佳實踐(第二版》讀書筆記與常見相關面試題總結 本節常見面試題(推薦帶著問題閱讀,問題答案在文中都有提到): 簡單說說類加載過...
摘要:虛擬機為了保證一個類的方法在多線程環境中被正確地加鎖同步。但啟動類加載器不可能認識這些代碼。實現模塊化熱部署的關鍵則是它的自定義類加載器機制的實現。 概念區分:加載、類加載、類加載器 類加載是一個過程。 加載(Loading)是類加載這一個過程的階段。 類加載器是ClassLoader類或其子類。 本文中的類的描述都包括了類和接口的可能性,因為每個Class文件都有可能代表J...
摘要:如果需要支持類的動態加載或需要對編譯后的字節碼文件進行解密操作等,就需要與類加載器打交道了。雙親委派模型,雙親委派模型,約定類加載器的加載機制。任何之類的字節碼都無法調用方法,因為該方法只能在類加載的過程中由調用。 jvm系列 垃圾回收基礎 JVM的編譯策略 GC的三大基礎算法 GC的三大高級算法 GC策略的評價指標 JVM信息查看 GC通用日志解讀 jvm的card table數據...
摘要:驗證驗證是連接階段的第一步,這一階段的目的是為了確保文件的字節流中包含的信息符合當前虛擬機的要求,并且不會危害虛擬機自身的安全。字節碼驗證通過數據流和控制流分析,確定程序語義是合法的符合邏輯的。 看過這篇文章,大廠面試你「雙親委派模型」,硬氣的說一句,你怕啥? 讀該文章姿勢 打開手頭的 IDE,按照文章內容及思路進行代碼跟蹤與思考 手頭沒有 IDE,先收藏,回頭看 (萬一哪次面試問...
摘要:類加載過程雙親委派模型聲明文章均為本人技術筆記,轉載請注明出處類加載過程類加載機制將類描述數據從文件中加載到內存,并對數據進行,解析和初始化,最終形成被直接使用的類型。深入理解虛擬機高級特性與最佳實踐加載加載階段由類加載器負責,過程見類加載 JVM類加載過程 & 雙親委派模型 聲明 文章均為本人技術筆記,轉載請注明出處https://segmentfault.com/u/yzwall ...
閱讀 2306·2021-11-23 10:09
閱讀 2885·2021-10-12 10:11
閱讀 2594·2021-09-29 09:35
閱讀 1337·2019-08-30 15:53
閱讀 2261·2019-08-30 11:15
閱讀 2904·2019-08-29 13:01
閱讀 2290·2019-08-28 18:15
閱讀 3363·2019-08-26 12:13