詳解jvm雙親委派機(jī)制
雙親委派機(jī)制
?記錄一下JVM的雙親委派機(jī)制學(xué)習(xí)記錄。
類(lèi)加載器種類(lèi)
?當(dāng)我們運(yùn)行某一個(gè)java類(lèi)的main方法時(shí),首先需要由java虛擬機(jī)的類(lèi)加載器將我們要執(zhí)行的main方法所在的class文件加載到j(luò)vm中,這里提到的類(lèi)加載器大概有4種:
引導(dǎo)類(lèi)加載器:負(fù)責(zé)加載支撐JVM運(yùn)行的位于JRE的lib目錄下的核心類(lèi)庫(kù),比如rt.jar、charsets.jar等
擴(kuò)展類(lèi)加載器:負(fù)責(zé)加載支撐JVM運(yùn)行的位于JRE的lib目錄下的ext擴(kuò)展目錄中的JAR類(lèi)包
應(yīng)用程序類(lèi)加載器:負(fù)責(zé)加載ClassPath路徑下的類(lèi)包,主要就是加載你自己寫(xiě)的那些類(lèi)
自定義加載器:負(fù)責(zé)加載用戶(hù)自定義路徑下的類(lèi)包。
?每個(gè)類(lèi)加載器加載的包路徑都是不同的,有各自的職責(zé)。通過(guò)一下示例,可以看出每個(gè)類(lèi)加載器加載的路徑:
public class TestJDKClassLoader { public static void main(String[] args) { System.out.println(String.class.getClassLoader()); System.out.println(com.sun.crypto.provider.DESKeyFactory.class.getClassLoader().getClass().getName()); System.out.println(TestJDKClassLoader.class.getClassLoader().getClass().getName()); System.out.println(); ClassLoader appClassLoader = ClassLoader.getSystemClassLoader(); ClassLoader extClassloader = appClassLoader.getParent(); ClassLoader bootstrapLoader = extClassloader.getParent(); System.out.println("the bootstrapLoader : " + bootstrapLoader); System.out.println("the extClassloader : " + extClassloader); System.out.println("the appClassLoader : " + appClassLoader); System.out.println(); System.out.println("bootstrapLoader加載以下文件:"); URL[] urls = Launcher.getBootstrapClassPath().getURLs(); for (int i = 0; i < urls.length; i++) { System.out.println(urls[i]); } System.out.println(); System.out.println("extClassloader加載以下文件:"); System.out.println(System.getProperty("java.ext.dirs")); System.out.println(); System.out.println("appClassLoader加載以下文件:"); System.out.println(System.getProperty("java.class.path")); } } // 運(yùn)行結(jié)果: null sun.misc.Launcher$ExtClassLoader sun.misc.Launcher$AppClassLoader the bootstrapLoader : null the extClassloader : sun.misc.Launcher$ExtClassLoader@330bedb4 the appClassLoader : sun.misc.Launcher$AppClassLoader@14dad5dc bootstrapLoader加載以下文件: file:/D:/ProgramFiles/jdk1.8.0_45_64bit/jre/lib/resources.jar file:/D:/ProgramFiles/jdk1.8.0_45_64bit/jre/lib/rt.jar file:/D:/ProgramFiles/jdk1.8.0_45_64bit/jre/lib/sunrsasign.jar file:/D:/ProgramFiles/jdk1.8.0_45_64bit/jre/lib/jsse.jar file:/D:/ProgramFiles/jdk1.8.0_45_64bit/jre/lib/jce.jar file:/D:/ProgramFiles/jdk1.8.0_45_64bit/jre/lib/charsets.jar file:/D:/ProgramFiles/jdk1.8.0_45_64bit/jre/lib/jfr.jar file:/D:/ProgramFiles/jdk1.8.0_45_64bit/jre/classes extClassloader加載以下文件: D:\ProgramFiles\jdk1.8.0_45_64bit\jre\lib\ext;C:\WINDOWS\Sun\Java\lib\ext appClassLoader加載以下文件: D:\ProgramFiles\jdk1.8.0_45_64bit\jre\lib\charsets.jar;D:\ProgramFiles\jdk1.8.0_45_64bit\jre\lib\deploy.jar;D:\ProgramFiles\jdk1.8.0_45_64bit\jre\lib\ext\access-bridge-64.jar;D:\ProgramFiles\jdk1.8.0_45_64bit\jre\lib\ext\cldrdata.jar;D:\ProgramFiles\jdk1.8.0_45_64bit\jre\lib\ext\dnsns.jar;D:\ProgramFiles\jdk1.8.0_45_64bit\jre\lib\ext\jaccess.jar;D:\ProgramFiles\jdk1.8.0_45_64bit\jre\lib\ext\jfxrt.jar;D:\ProgramFiles\jdk1.8.0_45_64bit\jre\lib\ext\localedata.jar;D:\ProgramFiles\jdk1.8.0_45_64bit\jre\lib\ext\nashorn.jar;D:\ProgramFiles\jdk1.8.0_45_64bit\jre\lib\ext\sunec.jar;D:\ProgramFiles\jdk1.8.0_45_64bit\jre\lib\ext\sunjce_provider.jar;D:\ProgramFiles\jdk1.8.0_45_64bit\jre\lib\ext\sunmscapi.jar;D:\ProgramFiles\jdk1.8.0_45_64bit\jre\lib\ext\sunpkcs11.jar;D:\ProgramFiles\jdk1.8.0_45_64bit\jre\lib\ext\zipfs.jar;D:\ProgramFiles\jdk1.8.0_45_64bit\jre\lib\javaws.jar;D:\ProgramFiles\jdk1.8.0_45_64bit\jre\lib\jce.jar;D:\ProgramFiles\jdk1.8.0_45_64bit\jre\lib\jfr.jar;D:\ProgramFiles\jdk1.8.0_45_64bit\jre\lib\jfxswt.jar;D:\ProgramFiles\jdk1.8.0_45_64bit\jre\lib\jsse.jar;D:\ProgramFiles\jdk1.8.0_45_64bit\jre\lib\management-agent.jar;D:\ProgramFiles\jdk1.8.0_45_64bit\jre\lib\plugin.jar;D:\ProgramFiles\jdk1.8.0_45_64bit\jre\lib\resources.jar;D:\ProgramFiles\jdk1.8.0_45_64bit\jre\lib\rt.jar;D:\Files\learn\tuling\jvm-demo\target\classes;D:\ProgramFiles\ideaIU-2021.3.3.win\lib\idea_rt.jar Process finished with exit code 0
appClassLoader雖然打印的內(nèi)容雖然很多,但它只需要加載target目錄下的文件。
雙親委派機(jī)制
?雖然基本只有4種類(lèi)加載器,但這4種類(lèi)加載器之間是存在一定的關(guān)聯(lián)關(guān)系的。如下圖:
?加載某個(gè)類(lèi)時(shí)會(huì)先委托給父加載器尋找目標(biāo)類(lèi),找不到再委托上層父類(lèi)加載器加載,所有的父類(lèi)加載器在自己的加載類(lèi)路徑下都找不到目標(biāo)類(lèi),則在自己的類(lèi)加載路徑中尋找并載入目標(biāo)類(lèi)。
?比如上面的TestJDKClassLoader,首先會(huì)委托應(yīng)用程序類(lèi)加載,應(yīng)用程序類(lèi)加載器則委托擴(kuò)展類(lèi)加載器加載,擴(kuò)展類(lèi)加載器則委托引導(dǎo)類(lèi)加載器加載,引導(dǎo)類(lèi)加載器在它的類(lèi)加載路徑下找不到TestJDKClassLoader.class文件,則向下委托擴(kuò)展類(lèi)加載器加載,擴(kuò)展類(lèi)加載器加載不到則委托應(yīng)用程序類(lèi)加載器自己加載,于是應(yīng)用程序類(lèi)加載器在target目錄下找到并載入了TestJDKClassLoader.class文件。
我們來(lái)看下應(yīng)用程序類(lèi)加載器AppClassLoader加載類(lèi)的雙親委派機(jī)制源碼,AppClassLoader的loadClass方法最終會(huì)調(diào)用其父類(lèi)ClassLoader的loadClass方法,該方法的大體邏輯如下:
我們來(lái)看下應(yīng)用程序類(lèi)加載器AppClassLoader加載類(lèi)的雙親委派機(jī)制源碼,AppClassLoader的loadClass方法最終會(huì)調(diào)用其父類(lèi)ClassLoader的loadClass方法,該方法的大體邏輯如下:
我們來(lái)看下應(yīng)用程序類(lèi)加載器AppClassLoader加載類(lèi)的雙親委派機(jī)制源碼,AppClassLoader的loadClass方法最終會(huì)調(diào)用其父類(lèi)ClassLoader的loadClass方法,該方法的大體邏輯如下:
1.首先,檢查一下指定名稱(chēng)的類(lèi)是否已經(jīng)加載過(guò),如果加載過(guò)了,就不需要再加載,直接
返回。
2.如果此類(lèi)沒(méi)有加載過(guò),那么,再判斷一下是否有父加載器;如果有父加載器,則由父加
載器加載(即調(diào)用parent.loadClass(name, false);).或者是調(diào)用bootstrap類(lèi)加載器來(lái)加
載。
3.如果父加載器及bootstrap類(lèi)加載器都沒(méi)有找到指定的類(lèi),那么調(diào)用當(dāng)前類(lèi)加載器的
findClass方法來(lái)完成類(lèi)加載。
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { synchronized (getClassLoadingLock(name)) { // First, check if the class has already been loaded // 檢查當(dāng)前類(lèi)加載器是否已經(jīng)找到 Class<?> c = findLoadedClass(name); if (c == null) { long t0 = System.nanoTime(); try { // 有無(wú)父類(lèi)加載器 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; } }
為什么要設(shè)計(jì)雙親委派機(jī)制?
沙箱安全機(jī)制:自己寫(xiě)的java.lang.String.class類(lèi)不會(huì)被加載,這樣便可以防止核心
API庫(kù)被隨意篡改
避免類(lèi)的重復(fù)加載:當(dāng)父親已經(jīng)加載了該類(lèi)時(shí),就沒(méi)有必要子ClassLoader再加載一
次,保證被加載類(lèi)的唯一性
例子:比如我們自己新建了一個(gè)java.lang.String類(lèi),我們看能不能加載成功。
package java.lang; public class String { public static void main(String[] args) { System.out.println("============自己的類(lèi)加載器===="); } } // 執(zhí)行結(jié)果 錯(cuò)誤: 在類(lèi) java.lang.String 中找不到 main 方法, 請(qǐng)將 main 方法定義為: public static void main(String[] args) 否則 JavaFX 應(yīng)用程序類(lèi)必須擴(kuò)展javafx.application.Application
自定義類(lèi)加載器
自定義類(lèi)加載器只需要繼承 java.lang.ClassLoader 類(lèi),該類(lèi)有兩個(gè)核心方法,一個(gè)是loadClass(String, boolean),實(shí)現(xiàn)了雙親委派機(jī)制,還有一個(gè)方法是findClass,默認(rèn)實(shí)現(xiàn)是空方法,所以我們自定義類(lèi)加載器主要是重寫(xiě)findClass方法。
public class MyClassLoaderTest { static class MyClassLoader extends ClassLoader { private String classPath; public MyClassLoader(String classPath) { this.classPath = classPath; } private byte[] loadByte(String name) throws Exception { name = name.replaceAll("\\.", "/"); FileInputStream fis = new FileInputStream(classPath + "/" + name + ".class"); int len = fis.available(); byte[] data = new byte[len]; fis.read(data); fis.close(); return data; } protected Class<?> findClass(String name) throws ClassNotFoundException { try { byte[] data = loadByte(name); //defineClass將一個(gè)字節(jié)數(shù)組轉(zhuǎn)為Class對(duì)象,這個(gè)字節(jié)數(shù)組是class文件讀取后最終的字節(jié)數(shù)組。 return defineClass(name, data, 0, data.length); } catch (Exception e) { e.printStackTrace(); throw new ClassNotFoundException(); } } } public static void main(String[] args) throws Exception { //初始化自定義類(lèi)加載器,會(huì)先初始化父類(lèi)ClassLoader,其中會(huì)把自定義類(lèi)加載器的父加載器設(shè)置為應(yīng)用程序類(lèi)加載器AppClassLoader MyClassLoader classLoader = new MyClassLoader("D:/test"); //D盤(pán)創(chuàng)建 test/com/hyz/jvm 幾級(jí)目錄,將User類(lèi)的復(fù)制類(lèi)User1.class丟入該目錄 Class clazz = classLoader.loadClass("com.hyz.jvm.User1"); // Class clazz = classLoader.loadClass("java.lang.String"); Object obj = clazz.newInstance(); Method method = clazz.getDeclaredMethod("hello", null); method.invoke(obj, null); System.out.println(clazz.getClassLoader().getClass().getName()); } }
打破雙親委派機(jī)制
?假如我們的target下有一個(gè)User類(lèi),但是我們的程序代碼中需要去讀取D:/test/com/hyz/jvm/User.class類(lèi),根據(jù)雙親委派機(jī)制,肯定是會(huì)加載到target下的User類(lèi)的,要如何才能加載到D:/test下的User類(lèi)呢?
?那意味著我們需要去打破雙親委派機(jī)制。看AppClassLoader的類(lèi)加載邏輯,主要邏輯在父類(lèi)ClassLoader.loadClass()方法中,我們只需要在自定義的類(lèi)加載器中重寫(xiě)該方法即可。主要修改邏輯:如果類(lèi)型是com.hyz.jvm開(kāi)頭的類(lèi),則從自定義類(lèi)加載器中去讀取,否則委托給上層類(lèi)加載器加載。
/** * 32 * 重寫(xiě)類(lèi)加載方法,實(shí)現(xiàn)自己的加載邏輯,不委派給雙親加載 * 33 * @param name * 34 * @param resolve * 35 * @return * 36 * @throws ClassNotFoundException * 37 */ 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) { // If still not found, then invoke findClass in order // to find the class. long t1 = System.nanoTime(); if (!name.startsWith("com.hyz.jvm")) { c = this.getParent().loadClass(name); } else { c = findClass(name); } // this is the defining class loader; record the stats sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1); sun.misc.PerfCounter.getFindClasses().increment(); } if (resolve) { resolveClass(c); } return c; } }
?應(yīng)用到打破雙親委派機(jī)制的實(shí)際應(yīng)用場(chǎng)景是在Tomcat加載war包。比如war1用的是spring4版本,war2用的是spring5版本,那就意味著加載著2個(gè)war包不能用同一個(gè)類(lèi)加載器實(shí)例,需要各自指定一個(gè)自定義的類(lèi)加載器實(shí)例,各自去加載所需的spring版本庫(kù)文件。
總結(jié):雙親委派機(jī)制保證了核心類(lèi)的安全,確保不會(huì)被修改,也保證了不會(huì)加載到重復(fù)的字節(jié)碼文件。
到此這篇關(guān)于jvm雙親委派機(jī)制詳解的文章就介紹到這了,更多相關(guān)jvm雙親委派機(jī)制內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java關(guān)鍵字synchronized基本使用詳解
這篇文章主要給大家介紹了關(guān)于Java關(guān)鍵字synchronized基本使用的相關(guān)資料,synchronized可以用來(lái)同步靜態(tài)和非靜態(tài)方法,文中通過(guò)代碼介紹的非常詳細(xì),需要的朋友可以參考下2024-01-01Java的Spring框架中AOP項(xiàng)目的一般配置和部署教程
這篇文章主要介紹了Java的Spring框架中AOP項(xiàng)目的一般配置和部署教程,AOP面向方面編程的項(xiàng)目部署結(jié)構(gòu)都比較類(lèi)似,因而也被看作是Spring的一種設(shè)計(jì)模式使用,需要的朋友可以參考下2016-04-04在Struts2中如何將父類(lèi)屬性序列化為JSON格式的解決方法
本篇文章,小編將為大家介紹關(guān)于在Struts2中如何將父類(lèi)屬性序列化為JSON格式的解決方法,有需要的朋友可以參考一下2013-04-04詳解使用Spring?Data?repository進(jìn)行數(shù)據(jù)層的訪(fǎng)問(wèn)問(wèn)題
這篇文章主要介紹了使用Spring?Data?repository進(jìn)行數(shù)據(jù)層的訪(fǎng)問(wèn),抽象出Spring Data repository是因?yàn)樵陂_(kāi)發(fā)過(guò)程中,常常會(huì)為了實(shí)現(xiàn)不同持久化存儲(chǔ)的數(shù)據(jù)訪(fǎng)問(wèn)層而寫(xiě)大量的大同小異的代碼,本文給大家介紹的非常詳細(xì),需要的朋友參考下吧2022-06-06Java 基礎(chǔ) byte[]與各種數(shù)據(jù)類(lèi)型互相轉(zhuǎn)換的簡(jiǎn)單示例
這篇文章主要介紹了Java 基礎(chǔ) byte[]與各種數(shù)據(jù)類(lèi)型互相轉(zhuǎn)換的簡(jiǎn)單示例的相關(guān)資料,這里對(duì)byte[]類(lèi)型對(duì)long,int,double,float,short,cahr,object,string類(lèi)型相互轉(zhuǎn)換的實(shí)例,需要的朋友可以參考下2017-01-01Java實(shí)現(xiàn)Excel轉(zhuǎn)PDF的兩種方法詳解
使用具將Excel轉(zhuǎn)為PDF的方法有很多,在這里我給大家介紹兩種常用的方法:使用spire轉(zhuǎn)化PDF、使用jacob實(shí)現(xiàn)Excel轉(zhuǎn)PDF,分別應(yīng)對(duì)兩種不一樣的使用場(chǎng)景,需要的可以參考一下2022-01-01