国产亚洲精品福利在线无卡一,国产精久久一区二区三区,亚洲精品无码国模,精品久久久久久无码专区不卡

當(dāng)前位置: 首頁 > news >正文

期貨做程序化回測的網(wǎng)站網(wǎng)站網(wǎng)絡(luò)排名優(yōu)化方法

期貨做程序化回測的網(wǎng)站,網(wǎng)站網(wǎng)絡(luò)排名優(yōu)化方法,wordpress4.9.6 漏洞,哪個(gè)網(wǎng)站服務(wù)器比較好文章目錄 Jvm基本組成一.什么是JVM類的加載二.類的生命周期階段1:加載階段2:驗(yàn)證階段3:準(zhǔn)備階段4:解析階段5:初始化 三.類初始化時(shí)機(jī)四.類加載器1.引導(dǎo)類加載器(Bootstrap Class Loader)2.拓展類…

文章目錄

  • Jvm基本組成
  • 一.什么是JVM類的加載
  • 二.類的生命周期
    • 階段1:加載
    • 階段2:驗(yàn)證
    • 階段3:準(zhǔn)備
    • 階段4:解析
    • 階段5:初始化
  • 三.類初始化時(shí)機(jī)
  • 四.類加載器
    • 1.引導(dǎo)類加載器(Bootstrap Class Loader)
    • 2.拓展類加載器(Extension Class Loader)
    • 3.應(yīng)用程序類加載器(System Class Loader)
    • 4.自定義類加載器(Custom Class Loader)
    • 5.類加載器的關(guān)系
    • 6.JVM類加載策略
  • 五.雙親委派機(jī)制
    • 1.什么是雙親委派機(jī)制
    • 2.淺析ClassLoader類
    • 3.雙親委派模式具體實(shí)現(xiàn)
      • 3.1.loadClass(String name)
      • 3.2.findClass(String name)-(重點(diǎn))
      • 3.3.defineClass(byte[] b, int off, int len)
      • 3.4.resolveClass(Class??? c)
      • 3.5.URLClassLoader類
    • 4.設(shè)計(jì)雙親委派機(jī)制的目的
    • 5.ExtClassLoader和AppClassLoader
  • 六.Tomcat 中的類加載器
    • 1.Tomcat類加載器類型說明
    • 2.Tomcat為什么要打破雙親委派模型
    • 3.線程上下文類加載器
  • 七.如何打破雙親委派模型
  • 八.打破雙親委派模型的常見場景
  • 九.熱部署類加載器

Jvm基本組成

JVM 的結(jié)構(gòu)基本上由 5 部分組成:

  • 類加載器(ClassLoader):在 JVM 啟動(dòng)時(shí)或者類運(yùn)行時(shí)將需要的 class文件 解析后生成一個(gè)Class對象加載到到 JVM 中
  • 執(zhí)行引擎(ExcutionEngine):執(zhí)行引擎的任務(wù)是負(fù)責(zé)執(zhí)行 class 文件中包含的字節(jié)碼指令,相當(dāng)于實(shí)際機(jī)器上的 CPU
  • 內(nèi)存區(qū)(Memory Area):將內(nèi)存劃分成若干個(gè)區(qū)以模擬實(shí)際機(jī)器上的存儲(chǔ)、記錄和調(diào)度功能模塊,如實(shí)際機(jī)器上的各種功能的寄存器或者 PC 指針的記錄器等,由Heap、程序計(jì)數(shù)器、虛擬機(jī)棧、本地方法棧和方法區(qū)五部分組成。
  • 本地方法調(diào)用(Native Method Interface):調(diào)用 C 或 C++ 實(shí)現(xiàn)的本地方法的代碼返回結(jié)果
  • 垃圾收集器(Garbage Collection):負(fù)責(zé)將內(nèi)存區(qū)中無用對象的釋放,主要是堆內(nèi)存和方法區(qū)

JVM被分為三個(gè)主要的子系統(tǒng):類加載器子系統(tǒng)、運(yùn)行時(shí)數(shù)據(jù)區(qū)、執(zhí)行引擎
在這里插入圖片描述

一.什么是JVM類的加載

Java代碼編譯執(zhí)行過程

  1. 源碼編譯:通過Java源碼編譯器(Javac命令)將Java代碼編譯成Jvm字節(jié)碼(.class文件)
  2. 類加載:通過ClassLoader及其子類來完成Jvm的類加載
  3. 類執(zhí)行:字節(jié)碼被裝入內(nèi)存,進(jìn)入Jvm,被解釋器(Java命令)解釋執(zhí)行

在這里插入圖片描述

什么是類的加載

  • 從上圖來看其實(shí)可以一句話來解釋:類的加載指的是將類的.class文件中的二進(jìn)制數(shù)據(jù)讀入到內(nèi)存中,并對class文件中的數(shù)據(jù)進(jìn)行校驗(yàn)、轉(zhuǎn)換、解析、初始化等操作后,將其放在運(yùn)行時(shí)數(shù)據(jù)區(qū)方法區(qū)內(nèi),然后在區(qū)創(chuàng)建一個(gè) Java.lang.Class對象,封裝類在方法區(qū)內(nèi)的數(shù)據(jù)結(jié)構(gòu)。

二.類的生命周期

類從JVM加載到卸載出內(nèi),整個(gè)生命周期包括:加載、驗(yàn)證、準(zhǔn)備、解析、初始化、使用和卸載 7個(gè)階段

  • 類加載包含了前5個(gè),其中 驗(yàn)證、準(zhǔn)備、解析為連接。具體如圖:
    在這里插入圖片描述

階段1:加載

  • 通過一個(gè)類的全路徑查找此類字節(jié)碼文件,并利用class文件創(chuàng)建對應(yīng)的Class對象并加載到方法區(qū)

階段2:驗(yàn)證

  • 確保Class文件的字節(jié)流中包含信息符合虛擬機(jī)規(guī)范,而不會(huì)危害虛擬機(jī)自身運(yùn)行安全。主要包括4種驗(yàn)證,文件格式驗(yàn)證,元數(shù)據(jù)驗(yàn)證,字節(jié)碼驗(yàn)證,符號(hào)引用驗(yàn)證

階段3:準(zhǔn)備

  • 為類變量(即static修飾的字段變量)分配內(nèi)存并且設(shè)置該類變量的默認(rèn)值
    • (如static int i=5; 這里只將i初始化為0,至于5的值將在初始化時(shí)賦值),這里不包含用final修飾的static變量,因?yàn)?code>final在編譯的時(shí)候就會(huì)分配了,且這里不會(huì)為實(shí)例變量分配初始化類變量會(huì)分配在方法區(qū)中,而實(shí)例變量是會(huì)隨著對象一起分配到Java堆中。

階段4:解析

  • 解析階段是將常量池中的符號(hào)引用替換為直接引用的過程。
    • 將符號(hào)引用替換為直接引用,該階段會(huì)把一些靜態(tài)方法(符號(hào)引用,比如 main() 方法)替換為指向數(shù)據(jù)所存內(nèi)存的指針或句柄等(直接引用),這就是所謂的靜態(tài)鏈接過程(類加載期間完成),動(dòng)態(tài)鏈接是在程序運(yùn)行期間完成的將符號(hào)引用替換為直接引用。
    • 主要工作:類或接口的解析,字段解析,類方法解析,接口方法解析

例如:在com.xxx.Person類中引用了com.xxx.Animal類,在編譯階段Person類并不知道Animal的實(shí)際內(nèi)存地址,因此只能用com.xxx.Animal來代表Animal真實(shí)的內(nèi)存地址。而在解析階段,JVM可以通過解析該符號(hào)引用,來確定com.xxx.Animal類的真實(shí)內(nèi)存地址(如果該類未被加載過,則先加載)。

符號(hào)引用就是一組符號(hào)來描述目標(biāo),可以是任何字面量,而直接引用就是直接指向目標(biāo)的指針、相對偏移量或一個(gè)間接定位到目標(biāo)的句柄

階段5:初始化

初始化階段是類加載過程的最后一步,這一步才 真正開始執(zhí)行類中定義的Java程序代碼。

  • 即對類的靜態(tài)變量初始化為指定的值,執(zhí)行靜態(tài)代碼塊。

在之前的準(zhǔn)備階段,類中定義的static靜態(tài)變量已經(jīng)被賦過一次默認(rèn)值。而在初始化階段,則會(huì)調(diào)用類構(gòu)造器<clinit>來完成初始化操作,為靜態(tài)變量賦原始值。

  • 此處,需要注意的是類構(gòu)造器<clinit>類構(gòu)造方法<init>區(qū)別。
    • 類構(gòu)造方法<init>每創(chuàng)建一次對象,就自動(dòng)調(diào)用一次,而類構(gòu)造器<clinit>類似于一個(gè)無參的構(gòu)造函數(shù),只不過該函數(shù)是靜態(tài)修飾,只會(huì)初始化靜態(tài)所修飾的代碼。
public class Student{public static int x = 10;public String zz = "jiaboyan";static{System.out.println("12345");}
}
  • 類構(gòu)造器<clinit>由編譯器自動(dòng)收集類中的所有靜態(tài)變量的賦值動(dòng)作靜態(tài)語句塊static{}中的代碼合并產(chǎn)生的,編譯器收集的順序是由 語句在源文件中出現(xiàn)的順序所決定的

綜上所述,對于上面的例子來說,類構(gòu)造器<clinit>為:

public class Student{<clinit>{public static int x = 10;System.out.println("12345");}
}

在這里插入圖片描述

三.類初始化時(shí)機(jī)

JVM規(guī)范中并沒有強(qiáng)制約束何時(shí)進(jìn)行加載,但是規(guī)范嚴(yán)格規(guī)定了有且只有下列5種情況必須對類進(jìn)行初始化(加載、驗(yàn)證、準(zhǔn)備都會(huì)隨著發(fā)生)

  1. 遇到 new、getstatic、putstatic、invokestatic 這4條字節(jié)碼指令時(shí),如果類沒有進(jìn)行過初始化,則必須先觸發(fā)其初始化。

    最常見的生成這 4 條指令的場景是:1.使用 new 關(guān)鍵字實(shí)例化對象的時(shí)候;2.讀取或設(shè)置類的靜態(tài)字段(被 final 修飾、已在編譯器把結(jié)果放入常量池的靜態(tài)字段除外)的時(shí)候 3.調(diào)用一個(gè)類的靜態(tài)方法的時(shí)候。

  2. 使用 java.lang.reflect 包的方法對類進(jìn)行反射調(diào)用的時(shí)候,如果類沒有進(jìn)行初始化,則需要先觸發(fā)其初始化。

  3. 當(dāng)初始化一個(gè)類的時(shí)候,如果發(fā)現(xiàn)其父類還沒有進(jìn)行過初始化,則需要先觸發(fā)其父類的初始化。

  4. 當(dāng)虛擬機(jī)啟動(dòng)時(shí),用戶需要指定一個(gè)要執(zhí)行的主類(啟動(dòng)類)(包含 main() 方法的那個(gè)類),虛擬機(jī)會(huì)先初始化這個(gè)主類;

  5. 當(dāng)使用 JDK.7 的動(dòng)態(tài)語言支持時(shí),如果一個(gè) java.lang.invoke.MethodHandle 實(shí)例最后的解析結(jié)果為 REF_getStatic, REF_putStatic, REF_invokeStatic方法句柄,并且這個(gè)方法句柄所對應(yīng)的類沒有進(jìn)行過初始化,則需要先觸發(fā)其初始化;

以上 5 種場景中的行為稱為對一個(gè)類進(jìn)行主動(dòng)引用除此之外,所有引用類的方式都不會(huì)觸發(fā)初始化,稱為被動(dòng)引用。被動(dòng)引用的常見例子包括:

  1. 通過子類引用父類的靜態(tài)字段,不會(huì)導(dǎo)致子類初始化。
  2. 通過數(shù)組定義來引用類,不會(huì)觸發(fā)此類的初始化。該過程會(huì)對數(shù)組類進(jìn)行初始化,數(shù)組類是一個(gè)由虛擬機(jī)自動(dòng)生成的、直接繼承自 Object 的子類,其中包含了數(shù)組的屬性和方法。
  3. 常量在編譯階段會(huì)存入調(diào)用類的常量池中,本質(zhì)上并沒有直接引用到定義常量的類,因此不會(huì)觸發(fā)定義常量的類的初始化。

特殊情況

  • 當(dāng) Java 虛擬機(jī)初始化一個(gè)類時(shí),要求它所有的父類都被初始化,但這一條規(guī)則并不適用于接口。一個(gè)父接口并不會(huì)因?yàn)樗淖咏涌诨蛘邔?shí)現(xiàn)類的初始化而初始化,只有當(dāng)程序首次被使用特定接口的靜態(tài)變量時(shí),才會(huì)導(dǎo)致該接口的初始化。

代碼塊、靜態(tài)方法、構(gòu)造方法加載順序

public class Son extends Father{static {  System.out.println("子類靜態(tài)代碼塊"); }{  System.out.println("子類代碼塊");  }public Son() { System.out.println("子類構(gòu)造方法"); }public static void main(String[] args) {new Son();}}class Father{static { System.out.println("父類靜態(tài)代碼塊"); }{System.out.println("父類代碼塊");}public Father() { System.out.println("父類構(gòu)造方法");}public static void find() {System.out.println("靜態(tài)方法");}
}
//代碼塊和構(gòu)造方法執(zhí)行順序
//父類靜態(tài)代碼塊
//子類靜態(tài)代碼塊
//父類代碼塊
//父類構(gòu)造方法
//子類代碼塊
//子類構(gòu)造方法

四.類加載器

  • 類加載器的作用 : 通過類全限定名讀取類的二進(jìn)制字節(jié)流到JVM中,然后轉(zhuǎn)換為一個(gè)與目標(biāo)類對應(yīng)的java.lang.Class對象實(shí)例
    在這里插入圖片描述

1.引導(dǎo)類加載器(Bootstrap Class Loader)

  • 引導(dǎo)類加載器,也叫啟動(dòng)類加載器 主要加載的是JVM自身需要的類,該類加載使用C++實(shí)現(xiàn)的,是虛擬機(jī)自身的一部分,負(fù)責(zé)加載 <JAVA_HOME>\lib\ 目錄下的核心類庫或被 -Dbootclaspath 參數(shù)指定的類, 如: rt.jar, tool.jar 等

2.拓展類加載器(Extension Class Loader)

  • 拓展類加載器: 由Sun公司通過Java在sun.misc.Launcher$ExtClassLoader類中實(shí)現(xiàn), 負(fù)責(zé)加載 <JAVA_HOME>\lib\ext 擴(kuò)展目錄中的類庫 -Djava.ext.dirs 選項(xiàng)所指定目錄下的類和 jar包,開發(fā)者可以直接使用標(biāo)準(zhǔn)擴(kuò)展類加載器,
//Launcher$ExtClassLoader類中獲取路徑的代碼
private static File[] getExtDirs() {//加載<JAVA_HOME>/lib/ext目錄中的類庫String s = System.getProperty("java.ext.dirs");File[] dirs;if (s != null) {StringTokenizer st = new StringTokenizer(s, File.pathSeparator);int count = st.countTokens();dirs = new File[count];for (int i = 0; i < count; i++) {dirs[i] = new File(st.nextToken());}} else {dirs = new File[0];}return dirs;}

3.應(yīng)用程序類加載器(System Class Loader)

  • 應(yīng)用程序類加載器,也稱為系統(tǒng)類加載器:由Sun公司通過Java在sun.misc.Launcher$AppClassLoader中實(shí)現(xiàn), 負(fù)責(zé)加載 環(huán)境變量 CLASSPATH -Djava.class.path、java -cp所指定的目錄下的類和 jar 包??梢酝ㄟ^ClassLoader#getSystemClassLoader()獲取到該類加載器,開發(fā)者可以直接使用系統(tǒng)類加載器,該加載器是程序中默認(rèn)類加載器
public class MainClassLoaderTest {public static void main(String[] args) {System.out.println(String.class.getClassLoader());System.out.println(DESKeyFactory.class.getClassLoader());System.out.println(MainClassLoaderTest.class.getClassLoader());/*nullsun.misc.Launcher$ExtClassLoader@246b179dsun.misc.Launcher$AppClassLoader@18b4aac2*/System.out.println();ClassLoader appClassLoader = ClassLoader.getSystemClassLoader();//應(yīng)用程序類加載器ClassLoader extClassLoader = appClassLoader.getParent();//拓展類加載器ClassLoader bootstrapClassLoader = extClassLoader.getParent();//引導(dǎo)類加載器System.out.println("bootstrapClassLoader: " + bootstrapClassLoader);System.out.println("extClassLoader: " + extClassLoader);System.out.println("appClassLoader: " + appClassLoader);/*bootstrapClassLoader: nullextClassLoader: sun.misc.Launcher$ExtClassLoader@246b179dappClassLoader: sun.misc.Launcher$AppClassLoader@18b4aac2*///BootstrapLoader  父加載器 及 加載路徑System.out.println();System.out.println("bootstrapLoader 加載以下文件:");URL[] urls = Launcher.getBootstrapClassPath().getURLs();for (URL url : urls) {System.out.println(url);}/*bootstrapLoader 加載以下文件:file:/E:/dev_tools/jdk/jdk1.8.0_181/jre/lib/resources.jarfile:/E:/dev_tools/jdk/jdk1.8.0_181/jre/lib/rt.jarfile:/E:/dev_tools/jdk/jdk1.8.0_181/jre/lib/sunrsasign.jarfile:/E:/dev_tools/jdk/jdk1.8.0_181/jre/lib/jsse.jarfile:/E:/dev_tools/jdk/jdk1.8.0_181/jre/lib/jce.jarfile:/E:/dev_tools/jdk/jdk1.8.0_181/jre/lib/charsets.jarfile:/E:/dev_tools/jdk/jdk1.8.0_181/jre/lib/jfr.jarfile:/E:/dev_tools/jdk/jdk1.8.0_181/jre/classes*///ExtClassLoader 父加載器 及 加載路徑System.out.println();System.out.println("extClassLoader 加載以下文件:");System.out.println(System.getProperty("java.ext.dirs"));/*extClassLoader 加載以下文件:E:\dev_tools\jdk\jdk1.8.0_181\jre\lib\ext;C:\Windows\Sun\Java\lib\ext*///AppClassLoader父加載器 及 加載路徑System.out.println();System.out.println("appClassLoader 加載以下文件:");System.out.println(System.getProperty("java.class.path"));/*appClassLoader 加載以下文件:E:\dev_tools\jdk\jdk1.8.0_181\jre\lib\charsets.jar;省略.....E:\dev_tools\jdk\jdk1.8.0_181\jre\lib\rt.jar;省略E:\dev_tools\jdk\jdk1.8.0_181\jre\lib\resources.jar;省略E:\04_resource_study\java_base_demo\target\classes;省略E:\dev_tools\mavenLocalStorage\org\springframework\boot\spring-boot-starter-web\2.2.0.RELEASE\spring-boot-starter-web-2.2.0.RELEASE.jar;E:\dev_tools\mavenLocalStorage\com\alibaba\fastjson\1.2.78\fastjson-1.2.78.jar;E:\dev_tools\mavenLocalStorage\mysql\mysql-connector-java\8.0.18\mysql-connector-java-8.0.18.jar;*/}
}
  • 可看出,Launcher采用了單例設(shè)計(jì)模式,其中BootstrapClassLoader 、ExtClassLoader和AppClassLoader的掃描路徑分別對應(yīng)系統(tǒng)屬性sun.boot.class.path、java.ext.dirs、java.class.path

4.自定義類加載器(Custom Class Loader)

  • 自定義類加載器:負(fù)責(zé)加載用戶自定義包路徑下的類包通過 ClassLoader 的子類實(shí)現(xiàn) Class 的加載
  • 如何實(shí)現(xiàn)繼承抽象類ClassLoader重寫findClass方法,判斷當(dāng)前類的class文件是否已被加載
/*** 自定義類加載器*/
public class MyClassLoader extends ClassLoader {//包路徑private String path;//構(gòu)造方法,用于初始化Path屬性public MyClassLoader(String path) {this.path = path;}//重寫findClass方法,參數(shù)name表示要加載類的全類名(包名.類名)@Overrideprotected Class<?> findClass(String name) throws ClassNotFoundException {//檢查該類的class文件是否已被加載,如果已加載則返回class文件(字節(jié)碼文件)對象,如果沒有加載返回nullClass<?> loadedClass = findLoadedClass(name);//如果已加載直接返回該類的class文件(字節(jié)碼文件)對象if (loadedClass != null) {return loadedClass;}//字節(jié)數(shù)組,用于存儲(chǔ)class文件的字節(jié)流byte[] bytes = null;try {//獲取class文件的字節(jié)流bytes = getBytes(name);} catch (Exception e) {e.printStackTrace();}if (bytes == null) {throw new ClassNotFoundException();}//如果字節(jié)數(shù)組不為空,則將class文件加載到JVM中//將class文件加載到JVM中,返回class文件對象//字節(jié)碼數(shù)組加載到 JVM 的方法區(qū),并在 JVM 的堆區(qū)建立一個(gè)java.lang.Class對象的實(shí)例,用來封裝 Java 類相關(guān)的數(shù)據(jù)和方法return this.defineClass(name, bytes, 0, bytes.length);}//獲取class文件的字節(jié)流private byte[] getBytes(String name) throws Exception {//拼接class文件路徑 replace(".",File.separator) 表示將全類名中的"."替換為當(dāng)前系統(tǒng)的分隔符,File.separator返回當(dāng)前系統(tǒng)的分隔符String fileUrl = path + name.replace(".", File.separator) + ".class";//緩沖區(qū)byte[] buffer = new byte[1024];//輸入流InputStream fis = new FileInputStream(new File(fileUrl));//相當(dāng)于一個(gè)緩存區(qū),動(dòng)態(tài)擴(kuò)容,也就是隨著寫入字節(jié)的增加自動(dòng)擴(kuò)容ByteArrayOutputStream baos = new ByteArrayOutputStream();//循環(huán)將輸入流中的所有數(shù)據(jù)寫入到緩存區(qū)中int bytesNumRead = 0;// 讀取類文件的字節(jié)碼while ((bytesNumRead = fis.read(buffer)) != -1) {baos.write(buffer, 0, bytesNumRead);}baos.flush();baos.close();return baos.toByteArray();}public static void main(String[] args) throws IllegalAccessException, InstantiationException, ClassNotFoundException {String path = "E:/04_resource_study/java_base_demo/target/classes/";//創(chuàng)建自定義類加載器對象MyClassLoader classLoader = new MyClassLoader(path);System.out.println("MyDamageClassLoader的父加載器:" + classLoader.getParent());//返回加載的class對象Class<?> clazz = classLoader.findClass("com.demo.classload.ClassLoaderTest");//調(diào)用類的構(gòu)造方法創(chuàng)建對象Object o = clazz.newInstance();//輸出創(chuàng)建的對象System.out.println("創(chuàng)建的對象:"+o);//輸出當(dāng)前類加載器System.out.println("ClassLoaderTest當(dāng)前類加載器:"+clazz.getClassLoader());//輸出當(dāng)前類加載器的父類System.out.println("ClassLoaderTest當(dāng)前類加載器的父類:"+clazz.getClassLoader().getParent());//輸出當(dāng)前類加載器的父類的父類System.out.println("ClassLoaderTest當(dāng)前類加載器的父類的父類:"+clazz.getClassLoader().getParent().getParent());//輸出當(dāng)前類加載器的父類的父類的父類System.out.println("ClassLoaderTest當(dāng)前類加載器的父類的父類的父類:"+clazz.getClassLoader().getParent().getParent().getParent());/*執(zhí)行結(jié)果:MyDamageClassLoader的父加載器:sun.misc.Launcher$AppClassLoader@18b4aac2創(chuàng)建的對象:com.demo.classload.ClassLoaderTest@238e0d81ClassLoaderTest當(dāng)前類加載器:com.demo.classload.MyDamageClassLoader@7daf6eccClassLoaderTest當(dāng)前類加載器的父類:sun.misc.Launcher$AppClassLoader@18b4aac2ClassLoaderTest當(dāng)前類加載器的父類的父類:sun.misc.Launcher$ExtClassLoader@31221be2ClassLoaderTest當(dāng)前類加載器的父類的父類的父類:null*/}
}

為什么要自定義類加載器?

  1. 當(dāng)class文件不在ClassPath路徑下,默認(rèn)系統(tǒng)類加載器無法找到該class文件
  2. 當(dāng)一個(gè)class文件是通過網(wǎng)絡(luò)傳輸并且可能會(huì)進(jìn)行相應(yīng)的加密操作時(shí),需要先對class文件進(jìn)行相應(yīng)的解密后再加載到JVM內(nèi)存中
  3. 當(dāng)需要實(shí)現(xiàn)熱部署功能時(shí)(一個(gè)class文件通過不同的類加載器產(chǎn)生不同class對象從而實(shí)現(xiàn)熱部署功能)

5.類加載器的關(guān)系

  • JVM 的類加載器具有父子關(guān)系,但不是通過繼承來實(shí)現(xiàn)而是每個(gè)類加載器通過組合方式維護(hù)一個(gè) parent字段,指向父加載器。
  • 啟動(dòng)類加載器(Bootstrap Class Loader),由C++實(shí)現(xiàn),沒有父類。啟動(dòng)類加載器無法被java程序直接引用。
  • 拓展類加載器(Extension ClassLoader),由Java語言實(shí)現(xiàn),父類加載器為null(如果parent=null,則它的父級(jí)就是啟動(dòng)類加載器。)
  • 系統(tǒng)類加載器(AppClassLoader),由Java語言實(shí)現(xiàn),父類加載器為ExtClassLoader
  • 自定義類加載器(Custom Class Loader),父類加載器肯定為AppClassLoader。

6.JVM類加載策略

  • 按需加載: JVM對Class文件采用的是按需加載的方式,也就是說當(dāng)需要使用該類時(shí)才會(huì)將它的Class文件加載到內(nèi)存生成Class對象

  • 全盤負(fù)責(zé):當(dāng)一個(gè)類加載器負(fù)責(zé)加載某個(gè)Class時(shí),該Class所依賴的和引用的其他Class也將由該類加載器負(fù)責(zé)一起加載,除非顯示使用另外一個(gè)類加載器來載加載

  • 父類委托(雙親委派):先讓父類加載器試圖加載該類,只有在父類加載器無法加載該類時(shí)才嘗試從自己的類路徑中加載該類(為了避免不同的加載器 加載相同的類 出現(xiàn)重復(fù)字節(jié))

  • 緩存機(jī)制:緩存機(jī)制將會(huì)保證所有加載過的Class都會(huì)被緩存,當(dāng)程序中需要使用某個(gè)Class時(shí),類加載器先從緩存區(qū)尋找該Class,只有緩存區(qū)不存在,系統(tǒng)才會(huì)讀取該類對應(yīng)的二進(jìn)制數(shù)據(jù),并將其轉(zhuǎn)換成Class對象,存入緩存區(qū)。這就是為什么修改了Class后,必須重啟JVM,程序的修改才會(huì)生效

在JVM中表示兩個(gè)class對象是否為同一個(gè)類對象存在2個(gè)必要條件

  • 類的全限定名必須一致(即包名.類名)
  • 加載這個(gè)類的ClassLoader(指ClassLoader實(shí)例對象)必須相同。
    即JVM中兩個(gè)類對象(class對象)就算來源同一個(gè)Class文件,被同一個(gè)虛擬機(jī)所加載,但只要加載它們的ClassLoader實(shí)例對象不同,那么這兩個(gè)類對象也是不相等的,這是因?yàn)?code>不同的ClassLoader實(shí)例對象都擁有不同的獨(dú)立的類名稱空間,所以加載的class對象也會(huì)存在不同的類名空間中,但前提是重寫loadClass(), 因?yàn)殡p親委派模型第一步會(huì)通過Class c = findLoadedClass(name)從緩存查找,類全限定名相同則不會(huì)再次被加載,因此我們必須跳過緩存查詢才能重新加載class對象。當(dāng)然也可直接調(diào)用findClass()方法,這樣也避免從緩存查找.

五.雙親委派機(jī)制

1.什么是雙親委派機(jī)制

JVM 的類加載器具有父子關(guān)系,雙親委派機(jī)制是在Java 1.2后引入的,其工作原理的是 ,如果一個(gè)類加載器收到了類加載請求,它并不會(huì)自己先去加載,而是把這個(gè)請求委托給父類的加載器去執(zhí)行,如果父類加載器還存在其父類加載器,則進(jìn)一步向上委托,依次遞歸,請求最終將到達(dá)頂層的啟動(dòng)類加載器(Bootstrap Class Loader),如果啟動(dòng)類加載器可以完成類加載任務(wù),就成功返回,否則就一層一層向下委派子加載器去嘗試加載,這就是雙親委派模式,即每個(gè)兒子都很懶,每次有活就丟給父親去干,直到父親說這件事我也干不了時(shí),兒子自己想辦法去完成,這不就是傳說中的實(shí)力坑爹啊?

在這里插入圖片描述
如圖所示

  • 當(dāng)System ClassLoader拿到一個(gè)class 文件之后, 會(huì)先問父加載器Extension ClassLoader能不能加載
  • 當(dāng)Extension ClassLoader 接收到請求時(shí),會(huì)先問問他的父加載器BootStrap ClassLoader能不能加載
  • 如果 Bootstrap ClassLoader可以加載,則由Bootstrap ClassLoader來加載,如不能則由Extension ClassLoader來加載. 如果 Extension ClassLoader也加載不了的話,最后由System ClassLoader來加載.

那么采用這種模式有啥用呢?

  • 雙親委派機(jī)制保證類加載器,自下而上的委派,又自上而下的加載,保證每一個(gè)類在各個(gè)類加載器中都是同一個(gè)類。

2.淺析ClassLoader類

JVM 并不是在啟動(dòng)時(shí)就把所有的.class文件都加載一遍,而是程序在運(yùn)行過程中用到了這個(gè)類才去加載。除了啟動(dòng)類加載器外,其他所有類加載器都需要繼承抽象類ClassLoader,這個(gè)抽象類中定義了3個(gè)關(guān)鍵方法,理解清楚它們的作用和關(guān)系非常重要。

public abstract class ClassLoader {//委托的父類加載器private final ClassLoader parent;//name為類的全限定名(包名.類名)	public Class<?> loadClass(String name) throws ClassNotFoundException {return loadClass(name, false);}//name為類的全限定名(包名.類名)	,resolve如果為true,則在生成class對象的同時(shí)進(jìn)行解析相關(guān)操作protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {//同步控制:開啟并發(fā)加載的情況下,鎖對類對應(yīng)的鎖對象,否則為ClassLoader對象本身synchronized (getClassLoadingLock(name)) {//首先,檢查類是否已經(jīng)加載Class<?> c = findLoadedClass(name);//1.如果沒有被加載if (c == null) {long t0 = System.nanoTime();try {//如果有父加載器,則先委派給父加載器去加載(注意這里是遞歸調(diào)用)if (parent != null) {c = parent.loadClass(name, false);// 如果父加載器為空,則委托啟動(dòng)類加載器去加載 } else {c = findBootstrapClassOrNull(name);}} catch (ClassNotFoundException e) {//如果沒有從非空父加載器中找到類,則拋出類異常ClassNotFoundException }//2.如果父加載器沒加載成功,調(diào)用自己實(shí)現(xiàn)的findClass去加載if (c == null) {//如果仍然沒有找到,則調(diào)用findClass來查找類。long t1 = System.nanoTime();c = findClass(name);//這是定義類裝入器;記錄數(shù)據(jù)sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);sun.misc.PerfCounter.getFindClasses().increment();}}//是否生成class對象的同時(shí)進(jìn)行解析相關(guān)操作if (resolve) {resolveClass(c);}return c;}}//name為類的全限定名(包名.類名)	protected Class<?> findClass(String name){//1. 根據(jù)傳入的類名name,到在特定目錄下去尋找類文件,把.class文件讀入內(nèi)存//......省略具體實(shí)現(xiàn)......//2. 調(diào)用defineClass將字節(jié)數(shù)組轉(zhuǎn)成Class對象return defineClass(buf, off, len)}//將字節(jié)碼數(shù)組解析成一個(gè)Class對象,用native方法實(shí)現(xiàn)//b:class文件字節(jié)數(shù)組,off:開始讀取位置,len:每次字節(jié)數(shù)protected final Class<?> defineClass(byte[] b, int off, int len){//......省略具體實(shí)現(xiàn)......}//獲取類名對應(yīng)的鎖對象//ClassLoader并發(fā)加載是通過一個(gè)ConcurrentHashMap<String,Object>實(shí)現(xiàn)的,Key為類名,對應(yīng)的Value為一個(gè)new Object(),//所以它可以同時(shí)加載多個(gè)類,但同一個(gè)類重復(fù)加載時(shí)則可以鎖住。通過registerAsParallelCapable()可以啟用并發(fā)加載。//詳見https://blog.csdn.net/w1673492580/article/details/81912344protected Object getClassLoadingLock(String className) {Object lock = this;if (parallelLockMap != null) {Object newLock = new Object();lock = parallelLockMap.putIfAbsent(className, newLock);if (lock == null) {lock = newLock;}}return lock;}
}

從上面的代碼可以得到幾個(gè)關(guān)鍵信息:

  • JVM 的類加載器具有父子關(guān)系,但不是通過繼承來實(shí)現(xiàn)而是每個(gè)類加載器通過組合方式維護(hù)一個(gè) parent字段,指向父加載器。
    • AppClassLoader的parent是ExtClassLoader,ExtClassLoader的parent是BootstrapClassLoader,但是ExtClassLoader的parent=null
  • defineClass():通過調(diào)用 native 方法Java 類的字節(jié)碼解析成一個(gè) Class 對象。
  • findClass():用于找到.class文件并把.class文件讀到內(nèi)存得到字節(jié)碼數(shù)組,然后在方法內(nèi)部調(diào)用 defineClass方法得到 Class 對象
    • 子類必須實(shí)現(xiàn)findClass。
  • loadClass():主要職責(zé)就是實(shí)現(xiàn)雙親委派機(jī)制:首先檢查類是不是被加載過,如果加載過直接返回,否則委派給父加載器加載,這是一個(gè)遞歸調(diào)用,一層一層向上委派,最頂層的類加載器(啟動(dòng)類加載器)無法加載該類時(shí),再一層一層向下委派給子類加載器加載。
    如圖所示:
    在這里插入圖片描述

3.雙親委派模式具體實(shí)現(xiàn)

  • 可以看出頂級(jí)類加載器是抽象類ClassLoader類,除啟動(dòng)類加載器外,所有的類加載器都繼承自ClassLoader,這里主要介紹ClassLoader中幾個(gè)比較重要的方法。

在這里插入圖片描述

3.1.loadClass(String name)

該方法就是雙親委派機(jī)制的具體實(shí)現(xiàn),用于加載指定全限定名的類,由ClassLoader類實(shí)現(xiàn),在JDK1.2之后不建議開發(fā)者重寫但可以直接通過this.getClass().getClassLoder.loadClass("className") 調(diào)用,源碼如下:

  • 業(yè)務(wù)邏輯:當(dāng)類加載請求到來時(shí),先從緩存中查找該類對象,如果存在直接返回,如果不存在則交給該類加載器去的父加載器去加載倘若沒有父加載器則交給頂級(jí)啟動(dòng)類加載器去加載,最后倘若仍沒有找到,則使用findClass()方法去加載
	//name為類的全限定名(包名.類名)	public Class<?> loadClass(String name) throws ClassNotFoundException {return loadClass(name, false);}//name為類的全限定名(包名.類名)	,resolve如果為true,則在生成class對象的同時(shí)進(jìn)行解析相關(guān)操作protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {//同步鎖synchronized (getClassLoadingLock(name)) {//首先,檢查類是否已經(jīng)加載Class<?> c = findLoadedClass(name);//1.如果沒有被加載if (c == null) {long t0 = System.nanoTime();try {//如果有父加載器,則先委派給父加載器去加載(注意這里是遞歸調(diào)用)if (parent != null) {c = parent.loadClass(name, false);// 如果父加載器為空,則委托啟動(dòng)類加載器去加載 } else {c = findBootstrapClassOrNull(name);}} catch (ClassNotFoundException e) {//如果沒有從非空父加載器中找到類,則拋出類異常ClassNotFoundException }//2.如果父加載器沒加載成功,調(diào)用自己實(shí)現(xiàn)的findClass去加載if (c == null) {//如果仍然沒有找到,則調(diào)用findClass來查找類。long t1 = System.nanoTime();c = findClass(name);//這是定義類裝入器;記錄數(shù)據(jù)sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);sun.misc.PerfCounter.getFindClasses().increment();}}//是否生成class對象的同時(shí)進(jìn)行解析相關(guān)操作if (resolve) {resolveClass(c);}return c;}}

3.2.findClass(String name)-(重點(diǎn))

該方法在JDK1.2之后已不建議用戶去覆蓋loadClass()方法,而是建議把自定義的類加載邏輯寫在findClass()方法中

  1. findClass()在loadClass()中被調(diào)用的,在loadClass()中當(dāng)父加載器加載失敗后,則會(huì)調(diào)用自己的findClass()方法來完成類加載,這樣就可以保證自定義的類加載器也符合雙親委托模式
  2. ClassLoader類沒有實(shí)現(xiàn)findClass()方法,只是簡單的拋出ClassNotFoundException
    protected Class<?> findClass(String name) throws ClassNotFoundException {throw new ClassNotFoundException(name);//直接拋出異常
    }
    
  3. 在findClass()中把class文件讀到內(nèi)存得到字節(jié)碼數(shù)組后,需要在該方法內(nèi)部調(diào)用 defineClass()方法得到 Class 對象

3.3.defineClass(byte[] b, int off, int len)

該方法由ClassLoader類中實(shí)現(xiàn),用于將byte字節(jié)流解析成JVM能夠識(shí)別的Class對象(),通常與findClass()方法一起使用

  • 一般情況下,在自定義類加載器時(shí),會(huì)直接覆蓋ClassLoader的findClass()方法并重寫類加載規(guī)則,取得要加載類的字節(jié)碼后轉(zhuǎn)換成流,然后調(diào)用defineClass()方法生成類的Class對象
  • 直接調(diào)用defineClass()生成類的Class對象,該Class對象并沒有解析(也可以理解為鏈接階段,畢竟解析是鏈接的最后一步),其解析操作需要等待初始化階段進(jìn)行。

代碼示例

protected Class<?> findClass(String name) throws ClassNotFoundException {// 獲取類的字節(jié)數(shù)組byte[] classData = getClassData(name);  if (classData == null) {throw new ClassNotFoundException();} else {//使用defineClass生成class對象return defineClass(name, classData, 0, classData.length);}}

3.4.resolveClass(Class??? c)

  • 使用該方法可以使用類的Class對象創(chuàng)建完成也同時(shí)被解析。前面我們說鏈接階段主要是對字節(jié)碼進(jìn)行驗(yàn)證為類變量分配內(nèi)存并設(shè)置初始值同時(shí)將字節(jié)碼文件中的符號(hào)引用轉(zhuǎn)換為直接引用

3.5.URLClassLoader類

URLClassLoader是ClassLoade的具體實(shí)現(xiàn),并新增URLClassPath類輔助獲取Class字節(jié)碼流等功能,如果自定義類加載器時(shí),沒有太復(fù)雜的需求,可以直接繼承URLClassLoader類,這樣就可以避免自己去編寫findClass()方法及其獲取字節(jié)碼流的方式,使自定義類加載器編寫更加簡潔

在這里插入圖片描述

4.設(shè)計(jì)雙親委派機(jī)制的目的

  • 避免類的重復(fù)加載:當(dāng)父類加載器已經(jīng)加載類后子類加載器沒必要再次加載,保證了類加載的唯一性。

    • 雙親委派保證類加載器,自下而上的委派,又自上而下的加載,保證每一個(gè)類在各個(gè)類加載器中都是同一個(gè)類。
  • 保證 Java 核心庫的類型安全Java 核心庫中的類加載工作都是由啟動(dòng)類加載器統(tǒng)一來完成的。從而確保了Java 應(yīng)用所使用的都是同一個(gè)版本的 Java 核心類庫,他們之間是相互兼容的

    • java.lang.Object,存在于rt.jar中,無論哪個(gè)類加載器要加載這個(gè)類,最終都是委派給啟動(dòng)類加載器加載,因此Object類在程序的各種類加載器環(huán)境中都是同一個(gè)類。
  • 不同的類加載器可以為相同類(binary name)的類創(chuàng)建額外的命名空間。相同名稱的類可以并存在JVM中,只需要不同的類加載器來加載他們即可,不同的類加載器的類之間是不兼容的,這相當(dāng)于在JVM內(nèi)部創(chuàng)建了一個(gè)又一個(gè)相互隔離的Java類空間,這類技術(shù)在很多框架中得到了實(shí)際運(yùn)用。

5.ExtClassLoader和AppClassLoader

拓展類加載器ExtClassLoader和系統(tǒng)類加載器AppClassLoader都繼承自URLClassLoader,是sun.misc.Launcher的靜態(tài)內(nèi)部類。

  • sun.misc.Launcher主要被系統(tǒng)用于啟動(dòng)主應(yīng)用程序

在這里插入圖片描述

ExtClassLoader并沒有重寫loadClass()方法,這說明其遵循雙親委派模式,而AppClassLoader重載了loadCass()方法,但最終調(diào)用的還是父類loadClass()方法因此依然遵守雙親委派模式,重載方法源碼如下:

/*** Override loadClass 方法,新增包權(quán)限檢測功能*/public Class loadClass(String name, boolean resolve) throws ClassNotFoundException{int i = name.lastIndexOf('.');if (i != -1) {SecurityManager sm = System.getSecurityManager();if (sm != null) {sm.checkPackageAccess(name.substring(0, i));}}//依然調(diào)用父類的方法return (super.loadClass(name, resolve));}
  • 其實(shí)無論是ExtClassLoader還是AppClassLoader都繼承URLClassLoader類,因此它們都遵守雙親委托模型

六.Tomcat 中的類加載器

1.Tomcat類加載器類型說明

在這里插入圖片描述

在Java類加載器基礎(chǔ)上,Tomcat新增了幾個(gè)類加載器,包括3個(gè)基礎(chǔ)類加載器和每個(gè)Web應(yīng)用的私有類加載器,其中3個(gè)基礎(chǔ)類加載器可在conf/catalina.properties中配置,具體介紹下:

  • Common Class Loader::以System Class Loader 為父類,是tomcat頂層的公用類加載器,其路徑由conf/catalina.properties中的common.loader指定,默認(rèn)加載$CATALINE_HOME/lib下的包。

  • Catalina Class Loader:以Common Class Loader 為父類,是用于加載Tomcat應(yīng)用服務(wù)器的類加載器,其路徑由server.loader指定,默認(rèn)為空,此時(shí)tomcat使用Common Class Loader加載應(yīng)用服務(wù)器。

  • Shared ClassLoader:以Common Class Loader 為父類,是所有Web應(yīng)用的父加載器,其路徑由shared.loader指定,默認(rèn)為空,此時(shí)Tomcat使用Common Class Loader類加載器作為Web應(yīng)用的父加載器。

  • Webapp ClassLoader:Shared ClassLoader為父類,加載/WEB-INF/classes目錄下的未壓縮的Class資源文件以及 /WEB-INF/lib目錄下的jar包是各個(gè) Webapp 私有的類加載器, 加載路徑中的 class 只對當(dāng)前 webapp 可見

    • 好處:每個(gè)的Context(web應(yīng)用/war包)都使用獨(dú)立的ClassLoader來加載web應(yīng)用中的WEB-INF/libs目錄下的jar包, 因此不同的web應(yīng)用包不會(huì)沖突。如A應(yīng)用用的是spring 4.X , B應(yīng)用用的是spring 5.X , 他們可以在同一個(gè)tomcat中運(yùn)行

默認(rèn)情況下,Common、Catalina、Shared類加載器是同一個(gè),但可以配置3個(gè)不同的類加載器,使他們各司其職。

2.Tomcat為什么要打破雙親委派模型

  • 我們都知道一個(gè)Tomcat 是可以部署多個(gè) war 包的,如果部署的多個(gè)war包中依賴的Jar包是不同版本的,比如War包A依賴 Spring 4,War包B依賴 Spring5 ,這時(shí)根據(jù)雙親委派機(jī)制,Spring4首先被加載進(jìn)來,那么另一個(gè)依賴 Spring 5 的 War包B在加載時(shí)就不會(huì)再去加載 Spring 5 。因?yàn)橥脑驎?huì)直接給他返回已加載過的 Spring 4 。這時(shí)會(huì)出現(xiàn)版本不一致的問題。因此對于 Tomcat 來說他就需要 自己實(shí)現(xiàn)類加載器來打破雙親委派模型,并給每一個(gè)war包去生成一個(gè)自己對應(yīng)的類加載器

如何打破?

  • Tomcat在初始化的時(shí)候通過Thread.currentThread().setContextClassLoader(xx)設(shè)置成了Catalina ClassLoader,使用Catalina ClassLoader來加載Tomcat使用的類,當(dāng)Tomcat加載WebApp中的類時(shí)設(shè)置成了WebappClassLoader,而WebappClassLoader重寫了loadClass方法打破了雙親委派

Web應(yīng)用默認(rèn)的類加載順序是(打破了雙親委派規(guī)則):

  • 先從JVM的BootStrapClassLoader中加載。
  • 加載Web應(yīng)用下/WEB-INF/classes中的類。
  • 加載Web應(yīng)用下/WEB-INF/lib/中的jar包中的類。
  • 加載上面定義的System路徑下面的類。
  • 加載上面定義的Common路徑下面的類。

如果在配置文件中配置了<Loader delegate="true"/>,那么就是遵循雙親委派規(guī)則,加載順序如下:

  • 先從JVM的BootStrapClassLoader中加載。
  • 加載上面定義的System路徑下面的類。
  • 加載上面定義的Common路徑下面的類。
  • 加載Web應(yīng)用下/WEB-INF/classes中的類。
    加載Web應(yīng)用下/WEB-INF/lib/中的jar包中的類。

3.線程上下文類加載器

線程上下文類加載器(ContextClassLoader)是JDK 1.2開始引入一種類加載器傳遞機(jī)制??梢酝ㄟ^Thread.currentThread().setContextClassLoader(xx)方法給一個(gè)線程設(shè)置上下文類加載器,在該線程后續(xù)執(zhí)行過程中就能通過Thread.currentThread().getContextClassLoader()獲取該類加載器使用。

  • 該加載器的出現(xiàn)就是為了方便破壞雙親委派,如果沒有手動(dòng)設(shè)置上下文類加載器,線程將繼承其父線程的上下文類加載器默認(rèn)線程的上下文類加載器是系統(tǒng)類加載器(AppClassLoader), 在線程中運(yùn)行的代碼可以通過類加載器來加載類和資源

七.如何打破雙親委派模型

雙親委派模型是Java設(shè)計(jì)者推薦給開發(fā)者們的默認(rèn)類加載器實(shí)現(xiàn)方式。這個(gè)委派和加載順序是非強(qiáng)制性,可破壞的。主要有2種方式

  1. 自定義類加載器,繼承ClassLoader,重寫findClass(),然后再重寫loadClass()改變雙親委派的類加載順序。

  2. 通過線程上下文類加載器的傳遞性,讓父類加載器中調(diào)用子類加載器的加載動(dòng)作。

下面使用方式1,基于代碼 4.自定義類加載器(Custom Class Loader)重寫loadClass()即可

public class MyClassLoader extends ClassLoader {//......................省略其他代碼......................//重點(diǎn): 重寫loadClass()改變雙親委派的類加載順序@Overridepublic Class<?> loadClass(String name) throws ClassNotFoundException{//1.破壞雙親委派的位置:自定義類加載機(jī)制先委派給ExtClassLoader加載,ExtClassLoader再委派給BootstrapClassLoader,如果都加載不了,然后自定義類加載器加載,自定義類加載器加載不了才交給SystemClassLoaderClassLoader classLoader = getSystemClassLoader();while (classLoader.getParent() != null) {classLoader = classLoader.getParent();}Class<?> clazz = null;try {clazz = classLoader.loadClass(name);} catch (ClassNotFoundException e) {// Ignore}if (clazz != null) {return clazz;}//2.自己加載clazz = this.findClass(name);if (clazz != null) {return clazz;}// 3.自己加載不了,再調(diào)用父類loadClass,保持雙親委派模式return super.loadClass(name);}//......................省略其他代碼......................public static void main(String[] args) throws IllegalAccessException, InstantiationException, ClassNotFoundException {String path = "E:/04_resource_study/java_base_demo/target/classes/";//創(chuàng)建自定義類加載器對象MyClassLoader classLoader = new MyClassLoader(path);System.out.println("MyDamageClassLoader的父加載器:" + classLoader.getParent());//返回加載的class對象Class<?> clazz = classLoader.findClass("com.demo.classload.ClassLoaderTest");//調(diào)用類的構(gòu)造方法創(chuàng)建對象Object o = clazz.newInstance();//輸出創(chuàng)建的對象System.out.println("創(chuàng)建的對象:"+o);//輸出當(dāng)前類加載器System.out.println("ClassLoaderTest當(dāng)前類加載器:"+clazz.getClassLoader());//輸出當(dāng)前類加載器的父類System.out.println("ClassLoaderTest當(dāng)前類加載器的父類:"+clazz.getClassLoader().getParent());//輸出當(dāng)前類加載器的父類的父類System.out.println("ClassLoaderTest當(dāng)前類加載器的父類的父類:"+clazz.getClassLoader().getParent().getParent());//輸出當(dāng)前類加載器的父類的父類的父類System.out.println("ClassLoaderTest當(dāng)前類加載器的父類的父類的父類:"+clazz.getClassLoader().getParent().getParent().getParent());/*執(zhí)行結(jié)果:MyDamageClassLoader的父加載器:sun.misc.Launcher$AppClassLoader@18b4aac2創(chuàng)建的對象:com.demo.classload.ClassLoaderTest@2e5d6d97ClassLoaderTest當(dāng)前類加載器:com.demo.classload.MyDamageClassLoader@685f4c2eClassLoaderTest當(dāng)前類加載器的父類:sun.misc.Launcher$AppClassLoader@18b4aac2ClassLoaderTest當(dāng)前類加載器的父類的父類:sun.misc.Launcher$ExtClassLoader@238e0d81ClassLoaderTest當(dāng)前類加載器的父類的父類的父類:nullnull*/}
}

在這里插入圖片描述

重寫loadClass()后, 類加載時(shí)先委派給ExtClassLoader加載,ExtClassLoader再委派給BootstrapClassLoader,如果都加載不了,然后自定義類加載器加載,自定義類加載器加載不了才交給SystemClassLoader。

為什么不能直接讓自定義類加載器加載呢?

  • 不能!雙親委派的破壞只能發(fā)生在SystemClassLoader及其以下的加載委派順序,ExtClassLoader上面的雙親委派是不能破壞的!

    • 因?yàn)槿魏晤惗际抢^承自頂級(jí)類java.lang.Object,而加載一個(gè)類時(shí),也會(huì)加載繼承的類,如果該類中還引用了其他類,則按需加載,且類加載器都是加載當(dāng)前類的類加載器
  • 如ClassLoaderTest類只隱式繼承了Object,自定義MyDamageClassLoader 加載了ClassLoaderTest,也會(huì)加載Object。 如果loadClass()直接調(diào)用MyDamageClassLoader的findClass()會(huì)報(bào)錯(cuò)java.lang.SecurityException: Prohibited package name: java.lang

八.打破雙親委派模型的常見場景

目前比較常見的場景主要有:

  1. 線程上下文類加載器(Thread.currentThread().setContextClassLoader(xx)、Thread.currentThread().getContextClassLoader()),如:JDBC 使用線程上下文類加載器加載 Driver 實(shí)現(xiàn)類

  2. Tomcat 的多 Web 應(yīng)用程序

  3. OSGI 實(shí)現(xiàn)模塊化熱部署

九.熱部署類加載器

熱部署類加載器: 即利用同一個(gè)class文件不同的類加載器在內(nèi)存創(chuàng)建出兩個(gè)不同的class對象

  • JVM在加載類之前會(huì)檢測請求類是否已加載過(即在loadClass()方法中調(diào)用findLoadedClass()方法),如果被加載過,則直接從 緩存 獲取,不會(huì)重新加載。

  • 前面的自定義類加載器MyClassLoader通過直接調(diào)用findClass()方法已具備這個(gè)熱加載功能

    • 為什么要調(diào)用findClass()來實(shí)現(xiàn)熱加載,而不是loadClass()呢?
      • ClassLoader中默認(rèn)實(shí)現(xiàn)的loadClass()方法中調(diào)用findLoadedClass()方法進(jìn)行了檢測是否已被加載,因此直接調(diào)用findClass()方法就可以繞過重用class緩存問題, 當(dāng)然也可以重寫loadClass()方法,但強(qiáng)烈不建議這么干
  • 注意; 同一個(gè)class文件最多只能被同一個(gè)類加載器的實(shí)例調(diào)用findClass()加載一次,多次加載將報(bào)錯(cuò), 因此必須讓同一類加載器的不同實(shí)例加載同一個(gè)class文件,以實(shí)現(xiàn)所謂的熱部署。

public class FileClassLoader extends ClassLoader {/*** 根路徑*/private String rootDir;public FileClassLoader(String rootDir) {this.rootDir = rootDir;}/*** 重寫findClass邏輯** @param className* @return* @throws ClassNotFoundException*/@Overrideprotected Class<?> findClass(String className) throws ClassNotFoundException {//獲取類的class字節(jié)數(shù)組,用于存儲(chǔ)class文件的字節(jié)流byte[] bytes = null;try {//獲取class文件的字節(jié)流bytes = getBytes(className);} catch (Exception e) {e.printStackTrace();}//如果文件為空if (bytes == null) {throw new ClassNotFoundException();}//直接生成class對象return defineClass(className, bytes, 0, bytes.length);}//獲取class文件的字節(jié)流private byte[] getBytes(String className) throws IOException {// 讀取類文件的字節(jié)String path = classNameToPath(className);//緩沖區(qū)byte[] buffer = new byte[1024];//輸入流InputStream fis = new FileInputStream(new File(path));//相當(dāng)于一個(gè)緩存區(qū),動(dòng)態(tài)擴(kuò)容,也就是隨著寫入字節(jié)的增加自動(dòng)擴(kuò)容ByteArrayOutputStream baos = new ByteArrayOutputStream();//循環(huán)將輸入流中的所有數(shù)據(jù)寫入到緩存區(qū)中int bytesNumRead = 0;// 讀取類文件的字節(jié)碼while ((bytesNumRead = fis.read(buffer)) != -1) {baos.write(buffer, 0, bytesNumRead);}baos.flush();baos.close();return baos.toByteArray();}/*** 類文件的完全路徑* @param className* @return*/private String classNameToPath(String className) {//拼接class文件路徑 replace(".",File.separator) 表示將全類名中的"."替換為當(dāng)前系統(tǒng)的分隔符,File.separator返回當(dāng)前系統(tǒng)的分隔符return rootDir + File.separatorChar + className.replace('.', File.separatorChar) + ".class";}
}

測試方法

    public static void main(String[] args) {//類所在包的絕對路徑String rootDir = "E:/04_resource_study/java_base_demo/target/classes/";//類的全限定名String classPath = "com.demo.classload.ClassLoaderTest";//創(chuàng)建自定義文件類加載器FileClassLoader loaderA = new FileClassLoader(rootDir);FileClassLoader loaderB = new FileClassLoader(rootDir);try {//加載指定的class文件,調(diào)用loadClass()Class<?> objectL1 = loaderA.loadClass(classPath);Class<?> objectL2 = loaderB.loadClass(classPath);System.out.println("loadClass->object1:" + objectL1.hashCode());System.out.println("loadClass->object2:" + objectL2.hashCode());Class<?> object3 = loaderA.loadClass(classPath);Class<?> object4 = loaderB.loadClass(classPath);System.out.println("loadClass->obj3:" + object3.hashCode());System.out.println("loadClass->obj4:" + object4.hashCode());Class<?> object5 = loaderA.findClass(classPath);Class<?> object6 = loaderB.findClass(classPath);System.out.println("findClass->object5:" + object5.hashCode());System.out.println("findClass->object6:" + object6.hashCode());//執(zhí)行到這里會(huì)報(bào)錯(cuò): java.lang.LinkageError: loader (instance of  com/demo/classload/FileClassLoader): attempted  duplicate class definition for name: "com/demo/classload/ClassLoaderTest"//原因: 同一個(gè)class文件最多只能被同一個(gè)類加載器的實(shí)例調(diào)用`findClass()`加載一次,多次加載將`報(bào)錯(cuò)`, 因此必須讓`同一類加載器的不同實(shí)例加載同一個(gè)class文件`,以實(shí)現(xiàn)所謂的熱部署。// Class<?> object7 = loaderA.findClass(classPath);// Class<?> object8 = loaderB.findClass(classPath);FileClassLoader loaderC= new FileClassLoader(rootDir);FileClassLoader loaderD = new FileClassLoader(rootDir);Class<?> object7 = loaderC.findClass(classPath);Class<?> object8 = loaderD.findClass(classPath);System.out.println("findClass->object7:" + object7.hashCode());System.out.println("findClass->object8:" + object8.hashCode());} catch (Exception e) {e.printStackTrace();}// 執(zhí)行結(jié)果// loadClass->object1:1751075886// loadClass->object2:1751075886// loadClass->obj3:1751075886// loadClass->obj4:1751075886// findClass->object5:930990596// findClass->object6:1921595561// findClass->object7:87285178// findClass->object8:610998173}

結(jié)論:

  • 同一個(gè)類加載器不同實(shí)例調(diào)用loadClass()加載class,每次返回的都是同一個(gè)class實(shí)例
  • 同一個(gè)類加載器實(shí)例調(diào)用findClass()加載class,第2次調(diào)用會(huì)拋出異常 java.lang.LinkageError, 因此每次熱部署都要new一個(gè)新類加載器實(shí)例來調(diào)用findClass()加載class,以返回不同的class實(shí)例

熱部署加載監(jiān)控線程

  • 如何測試: 熱部署監(jiān)控線程啟動(dòng)時(shí),只需要我們調(diào)整type()方法中,println打印內(nèi)容,然后將重新編譯后的Pay.class文件放到指定類加載路徑中就行了
package com.demo.classload;
public class Pay {public void type() {System.out.println("微信支付");}
}
@Slf4j
public class HotDeploymentTest {public static void main(String[] args) throws InterruptedException {//記錄文件上次修改時(shí)間AtomicReference<Long> lastModified = new AtomicReference<>(0L);new Thread(() -> {while (true) {//類文件所在包根路徑String rootDir = "E:/04_resource_study/java_base_demo/target/classes/";//類文件所在包絕對路徑String classAbsolutePath = rootDir + "/com/demo/classload/Pay.class";//類的全限定名String classSourcePath = "com.demo.classload.Pay";try {File file = new File(classAbsolutePath);//文件不存在,線程休眠if (!file.exists()) {int sleep = 2000;log.info("文件{}不存在,休眠{}ms", file.getAbsolutePath(), sleep);TimeUnit.MILLISECONDS.sleep(sleep);continue;}//獲取文件的上次修改時(shí)間Long modified = file.lastModified();//如果文件上次修改時(shí)間發(fā)生改變,使用類加載器重載文件if (modified > lastModified.get()) {//當(dāng)前文件的文件上次修改時(shí)間lastModified.set(modified);log.info("文件{}存在且發(fā)生改變,lastModified:{},modified:{},開始熱部署", file.getAbsolutePath(), lastModified.get(), modified);//創(chuàng)建自定義文件類加載器FileClassLoader loader = new FileClassLoader(rootDir);//加載指定的class文件,直接調(diào)用findClass(),繞過檢測機(jī)制,創(chuàng)建不同class對象。Class<?> clazz = loader.findClass(classSourcePath);//調(diào)用類的構(gòu)造方法創(chuàng)建對象Object obj = clazz.newInstance();//獲取該對象的方法String methodName = "type";Method method = clazz.getMethod(methodName, null);//執(zhí)行方法method.invoke(obj,  null);}//沒有改變,線程休眠else {log.info("文件{}存在未發(fā)生改變,lastModified:{},modified:{}", file.getAbsolutePath(), lastModified.get(), modified);TimeUnit.MILLISECONDS.sleep(5000);}} catch (Exception e) {e.printStackTrace();}}}).start();}
}

執(zhí)行結(jié)果
在這里插入圖片描述

http://m.aloenet.com.cn/news/42350.html

相關(guān)文章:

  • 建設(shè)綜合信息網(wǎng)站需要多少錢如何廣告推廣
  • 國產(chǎn)一級(jí)a做爰片免費(fèi)網(wǎng)站哪個(gè)網(wǎng)站是免費(fèi)的
  • 網(wǎng)絡(luò)營銷模式包括哪些seo網(wǎng)站關(guān)鍵詞快速排名
  • 做網(wǎng)站放太多視頻seo項(xiàng)目分析
  • 十堰網(wǎng)站seo方法百度seo關(guān)鍵詞優(yōu)化公司
  • 做公司網(wǎng)站一般多少錢免費(fèi)軟件下載網(wǎng)站有哪些
  • 集團(tuán)網(wǎng)站建設(shè)方案書游戲推廣員是違法的嗎
  • 軟件開發(fā)步驟流程鄭州見效果付費(fèi)優(yōu)化公司
  • 廈門 微網(wǎng)站制作企業(yè)推廣策劃書
  • 做寵物食品的網(wǎng)站優(yōu)化落實(shí)疫情防控新十條
  • 上傳了網(wǎng)站源碼怎么做新聞最新熱點(diǎn)
  • 桓臺(tái)網(wǎng)站開發(fā)廣州:推動(dòng)優(yōu)化防控措施落地
  • 中國互聯(lián)網(wǎng)網(wǎng)站性能丈哥seo博客工具
  • 錦州網(wǎng)站建設(shè)多少錢網(wǎng)站排名掉了怎么恢復(fù)
  • wordpress 地址 .html臺(tái)州seo
  • 別人抄襲網(wǎng)站設(shè)計(jì)怎么辦設(shè)計(jì)師必備的6個(gè)網(wǎng)站
  • 尋花問柳一家專注做男人喜愛的網(wǎng)站什么網(wǎng)站推廣比較好
  • 諸城盟族網(wǎng)站建設(shè)北京做網(wǎng)站公司哪家好
  • 網(wǎng)上營銷活動(dòng)長沙網(wǎng)站seo分析
  • 學(xué)校校園網(wǎng)站建設(shè)方案上海網(wǎng)站營銷seo方案
  • 做圖片的網(wǎng)站外貿(mào)建站
  • 網(wǎng)站開發(fā)研究背景域名搜索
  • 做網(wǎng)站 警察佛山抖音seo
  • macos做網(wǎng)站快速網(wǎng)站推廣
  • 網(wǎng)站開發(fā)技術(shù)項(xiàng)目北京seo相關(guān)
  • 免費(fèi)做網(wǎng)站方案新手怎么做seo優(yōu)化
  • win2012 iis 部署網(wǎng)站運(yùn)營是做什么的
  • 網(wǎng)站轉(zhuǎn)化分析百度優(yōu)化怎么做
  • 大連市建委官方網(wǎng)站推廣一般收多少錢
  • java python 做網(wǎng)站武漢seo認(rèn)可搜點(diǎn)網(wǎng)絡(luò)