JVM雙親委派模型知識(shí)詳細(xì)總結(jié)
一、簡介
除了頂層的啟動(dòng)類加載器(Bootstrap ClassLoader)外,其余的類加載器都應(yīng)當(dāng)有自己的上層加載器,如果一個(gè)類加載器收到了類加載請(qǐng)求,它并不會(huì)自己先去加載,而是把這個(gè)請(qǐng)求委托給上層的加載器,如果上層類加載器還存在其上層類加載器,則進(jìn)一步向上委托,依次遞歸,直到請(qǐng)求最終到達(dá)頂層的啟動(dòng)類加載器,從頂層類加載器開始,如果類加載器根據(jù)類的全限定名查詢到已經(jīng)加載過這個(gè)類,就成功返回加載過的此類信息,倘若加載器未加載過此類,則原路返回給下層加載器繼續(xù)重復(fù)此過程,直到最先加載此類的加載器所有上層加載器都未加載過此類后,此類加載器才會(huì)嘗試自己去加載,這便是雙親委派模式。
舉個(gè)栗子:
假如你是某個(gè)企業(yè)員工,你寫了一份方案希望得到執(zhí)行,首先你得拿給你的經(jīng)理去審批吧,經(jīng)理說這個(gè)事情他做不了主,于是經(jīng)理就拿給總經(jīng)理看,總經(jīng)理也做不了主,就給董事長看,然后董事長看了看,也看不明白于是讓總經(jīng)理自己拿主意吧,總經(jīng)理仔細(xì)一看,這個(gè)方案之前公司已經(jīng)做過了,于是讓經(jīng)理去告訴那個(gè)員工不用做了,直接用那個(gè)做過的方案吧。
二、雙親委派的意義
采用雙親委派模式的是好處是Java類隨著它的類加載器一起具備了一種帶有優(yōu)先級(jí)的層次關(guān)系,通過這種層級(jí)關(guān)系可以避免類的重復(fù)加載,當(dāng)上層類加載器已經(jīng)加載了該類時(shí),就沒有必要下層的ClassLoader再加載一次。
其次是考慮到安全因素,jdk中定義的類不會(huì)被隨意替換,假設(shè)我們在classpath路徑下自定義一個(gè)名為java.lang.Integer的類,通過雙親委托模式傳遞到啟動(dòng)類加載器,而啟動(dòng)類加載器通過索引發(fā)現(xiàn)同全限定名的類已被加載,并不會(huì)重新加載網(wǎng)絡(luò)傳遞的過來的java.lang.Integer,而直接返回已加載過的Integer.class,那么你所定義的類就不會(huì)被加載,這樣便可以防止核心API庫被隨意篡改。
如:
package java.lang; public class Integer { public void print(){ System.out.println("this is Integer."); } public static void main(String[] args) { new Integer().print(); } }
執(zhí)行main方法后輸出如下:
三、JVM提供的類加載器
- 啟動(dòng)類加載器(BootstrapClassLoader):由jvm負(fù)責(zé)管理,主要負(fù)責(zé)加載核心的類庫(**rt.jar,也就是java.lang.***等),并構(gòu)造和啟動(dòng)ExtClassLoader和APPClassLoader。
- 擴(kuò)展類加載器(ExtClassLoader):主要負(fù)責(zé)加載jre/lib/ext**目錄下的一些擴(kuò)展的jar。
- 應(yīng)用類加載器(AppClassLoader):主要負(fù)責(zé)加載classpath下jar包和應(yīng)用程序的主函數(shù)類。
如果當(dāng)前類加載器加載的類引用了其它類,那么也會(huì)通過遞歸的方式先對(duì)其所有引用進(jìn)行加載。
四、執(zhí)行類加載的五種方式
認(rèn)識(shí)了這三種類加載器,接下來我們看看類加載的五種方式。
1.通過命令行使用java命令啟動(dòng)應(yīng)用時(shí)由JVM初始化加載含有main()方法的主類。
2.使用new關(guān)鍵字初始化一個(gè)對(duì)象時(shí)
3.通過類對(duì)應(yīng)的Class對(duì)象的newInstance()方法
4.通過Class.forName(name)方法動(dòng)態(tài)加載
5.通過ClassLoader.loadClass(name)方法動(dòng)態(tài)加載
五、自定義類加載器
java系統(tǒng)為我們提供的三種類加載器,還給出了他們的層次關(guān)系圖,最下面就是自定義類加載器,那么我們?nèi)绾巫约憾x類加載器呢?這主要有兩種方式
(1)遵守雙親委派模型:繼承ClassLoader,重寫findClass方法。
通常我們推薦采用此方式自定義類加載器,最大程度上的遵守雙親委派模型。
findClass(name)查找具有指定全限定名的類。此方法用于遵循加載類的委托模型的類裝入器實(shí)現(xiàn)重寫,并將在檢查請(qǐng)求類的父類裝入器之后由loadClass方法調(diào)用。如果該類沒有被加載過且其class文件讀取不到則拋出ClassNotFoundException異常。
protected Class<?> findClass(String name) throws ClassNotFoundException { throw new ClassNotFoundException(name); }
其實(shí)現(xiàn)例子:
package com.aliencat.javabase.classloader; public class MyClassLoader extends ClassLoader{ protected Class<?> findClass(String name) throws ClassNotFoundException { switch (name){ case "java.lang.Integer":return Double.class; case "com.aliencat.javabase.classloader.LoaderDemo" : return loadClassFromDisk(name); } throw new ClassNotFoundException(name); } //從calsspath下加載類的字節(jié)碼文件 public byte[] loadClassFromDisk(String name) { String classPathRoot = Thread.currentThread().getContextClassLoader().getResource("").getPath(); classPathRoot = classPathRoot.substring(1); String filePath = classPathRoot + name.replace(".","/") + ".class"; try(InputStream in = new FileInputStream(filePath) ; ByteOutputStream stream = new ByteOutputStream()) { byte[] buff = new byte[1024]; for(int num = 0; (num=in.read(buff)) != -1;){ stream.write(buff,0,num); } return stream.toByteArray(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } return null; } public static void main(String[] args) throws ClassNotFoundException { Class clzz = new MyClassLoader().loadClass("com.aliencat.javabase.classloader.LoaderDemo"); System.out.println(clzz); clzz = new MyClassLoader().loadClass("java.lang.Integer"); System.out.println(clzz); clzz = new MyClassLoader().loadClass("java.lang.String"); System.out.println(clzz); clzz = new MyClassLoader().loadClass("java.lang.xxxxx"); System.out.println(clzz); } }
輸出如下:
(2)破壞雙親委派模型:繼承ClassLoader,重寫loadClass方法。
我們先來看下loadClass方法的源碼是怎樣的:
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { synchronized (getClassLoadingLock(name)) { // 首先,在當(dāng)前加載器加載過的類中檢查這個(gè)類有沒有被加載過 Class<?> c = findLoadedClass(name); if (c == null) { long t0 = System.nanoTime(); try { if (parent != null) { //存在上層類加載器,則讓上層取執(zhí)行l(wèi)oadClass方法 c = parent.loadClass(name, false); } else { //讓BootstrapClass類加載器去查找該類 c = findBootstrapClassOrNull(name); } } catch (ClassNotFoundException e) { // 上層類加載器拋出了ClassNotFoundException 則不進(jìn)行處理 } if (c == null) { long t1 = System.nanoTime(); // 如果上層類加載器都沒有找到 // 那么這個(gè)類加載器自己去找 // 如果找到了,則將resolve置為true c = findClass(name); // 這是定義類加載器;記錄統(tǒng)計(jì)數(shù)據(jù) sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0); sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1); sun.misc.PerfCounter.getFindClasses().increment(); } } if (resolve) { //解析此類的信息 resolveClass(c); } return c; } }
示例如下:
public class MyClassLoader extends ClassLoader { public static void main(String[] args) throws ClassNotFoundException { Class clzz = new MyClassLoader().loadClass("com.aliencat.javabase.classloader.ClassTest"); System.out.println(clzz); clzz = new MyClassLoader().loadClass("java.lang.Double"); System.out.println(clzz); clzz = new MyClassLoader().loadClass("java.lang.String"); System.out.println(clzz); } protected Class<?> findClass(String name) throws ClassNotFoundException { switch (name) { case "java.lang.Double": return Integer.class; case "java.lang.Object": //如果去掉此項(xiàng),則破壞雙親委任的情況下會(huì)報(bào)找不到Object的NoClassDefFoundError異常 return Object.class; case "com.aliencat.javabase.classloader.ClassTest": byte[] bytes = loadClassFromDisk(name); if(bytes != null){ return defineClass(name,bytes,0,bytes.length); }else { return null; } } throw new ClassNotFoundException(name); } //從calsspath下加載類的字節(jié)碼文件 public byte[] loadClassFromDisk(String name) { String classPathRoot = Thread.currentThread().getContextClassLoader().getResource("").getPath(); classPathRoot = classPathRoot.substring(1); String filePath = classPathRoot + name.replace(".","/") + ".class"; try(InputStream in = new FileInputStream(filePath) ; ByteOutputStream stream = new ByteOutputStream()) { byte[] buff = new byte[1024]; for(int num = 0; (num=in.read(buff)) != -1;){ stream.write(buff,0,num); } return stream.toByteArray(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } return null; } protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { synchronized (LoaderDemo.class) { // 首先,在當(dāng)前加載器加載過的類中檢查這個(gè)類有沒有被加載過 Class<?> c = findLoadedClass(name); if (c == null) { //沒加載過的話就去磁盤對(duì)應(yīng)路徑下去找 c = findClass(name); } return c; } } } class ClassTest{ }
輸出如下:
所以破壞雙親委托的方法簡單來說就是通過繼承ClassLoader重寫loadClass方法,去掉其中委托給上級(jí)加載類的相關(guān)邏輯然后實(shí)現(xiàn)自定義的加載類的findClass邏輯。(另外你可以試試把ClassTest替換String的類名是什么效果哦,我就不演示了)
六、總結(jié)
Q:前面說了一堆雙親委托的好處,那么為什么要破壞雙親委托呢?
A:因?yàn)樵谀承┣闆r下父類加載器需要委托子類加載器去加載class文件。受到加載范圍的限制,父類加載器無法加載到需要的文件,以JDBC接口為例,由于JDBC接口定義在jdk當(dāng)中的,而其實(shí)現(xiàn)由各個(gè)數(shù)據(jù)庫的服務(wù)商來提供,比如mysql的就寫了MySQL-Connector
,那么問題就來了,JDBC接口由啟動(dòng)類加載器加載,而JDBC的實(shí)現(xiàn)是由服務(wù)商提供的,并不在啟動(dòng)類加載器的加載目錄下,在不破壞雙親委派的情況下,啟動(dòng)類加載器下層的類加載器要加載JDBC則必然會(huì)返回啟動(dòng)類加載器中已經(jīng)加載過的接口,那么服務(wù)商提供的JDBC就不會(huì)被加載,所以需要自定義類加載器破壞雙親委派拿到服務(wù)商實(shí)現(xiàn)的JDBC接口,這里僅僅是舉了破壞雙親委派的其中一個(gè)情況,類似的還有tomcat。
到此這篇關(guān)于JVM雙親委派模型知識(shí)詳細(xì)總結(jié)的文章就介紹到這了,更多相關(guān)JVM雙親委派模型內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Mybatis實(shí)戰(zhàn)教程之入門到精通(經(jīng)典)
MyBatis是支持普通SQL查詢,存儲(chǔ)過程和高級(jí)映射的優(yōu)秀持久層框架,通過本文給大家介紹Mybatis實(shí)戰(zhàn)教程之入門到精通,對(duì)mybatis實(shí)戰(zhàn)教程相關(guān)知識(shí)感興趣的朋友一起學(xué)習(xí)吧2016-01-01Spring中的策略模式簡單實(shí)現(xiàn)與使用分析
這篇文章主要介紹了Spring中的策略模式簡單實(shí)現(xiàn)與使用分析,去初始化時(shí)除了?initMultipartResolver(上傳文件)沒有獲取?Properties?defaultStrategies;默認(rèn)策略,其他的八大件都會(huì)使用到策略模式,需要的朋友可以參考下2024-01-01淺析springboot通過面向接口編程對(duì)控制反轉(zhuǎn)IOC的理解
這篇文章主要介紹了springboot通過面向接口編程對(duì)控制反轉(zhuǎn)IOC的理解,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧2020-08-08Java中的 FilterInputStream簡介_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理
FilterInputStream 的作用是用來“封裝其它的輸入流,并為它們提供額外的功能”。接下來通過本文給大家分享Java中的 FilterInputStream簡介,感興趣的朋友一起學(xué)習(xí)吧2017-05-05MyBatisPlus中@TableField注解的基本使用
這篇文章主要介紹了MyBatisPlus中@TableField注解的基本使用,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-07-07play for scala 實(shí)現(xiàn)SessionFilter 過濾未登錄用戶跳轉(zhuǎn)到登錄頁面
這篇文章主要介紹了play for scala 實(shí)現(xiàn)SessionFilter 過濾未登錄用戶跳轉(zhuǎn)到登錄頁面的相關(guān)資料,需要的朋友可以參考下2016-11-11云計(jì)算實(shí)驗(yàn):Java?MapReduce編程
這篇文章主要介紹了云計(jì)算實(shí)驗(yàn):Java?MapReduce編程,?居于Java圍繞MapReduce編程展開詳細(xì)內(nèi)容,文章助大家掌握MapReduce編程,理解MapReduce原理,需要的朋友可以參考一下2021-12-12