文章标題
- 一、類加載機制
- 2.1、為什麼要設計雙親委派機制?
- 2.2、全盤負責委托機制
- 2.3、自定義類加載器示例
- 2.4、如何打破雙親委派機制呢
- 1.1、證明
- 1.2、類加載器和雙親委派機制
- 1.3、加載器初始化過程:
- 1、類加載運作全過程
- 2、雙親委派機制
當我們用java指令運作某個類的main函數啟動程式時,首先需要通過類加載器把主類加載到JVM。
package com.zhz.jvm;/**
1. @author :zhz
2. @date :Created in 2020/12/31
3. @version: V1.0
4. @slogan: 天下風雲出我輩,一入代碼歲月催
5. @description:
**/public class TestMath {public static final int data=0;public static Person p=new Person();//一個方法對應一塊棧幀區域public int compute(){int a=1;int b=1;int c=(a+b)*10;return c;}public static void main(String[] args) {TestMath math=new TestMath();math.compute();}}
通過Java指令執行代碼的大體流程如下:

類加載過程分為
加載 >> 驗證 >> 準備 >> 解析 >> 初始化 >> 使用 >> 解除安裝1、加載
在硬碟上查找并通過IO讀入位元組碼檔案,使用到類時才會加載,例如調用類的main()方法,new對象
等等,在加載階段會在記憶體中生成一個代表這個類的java.lang.Class對象,作為方法區這個類的各種資料的通路入口2、驗證
校驗位元組碼檔案的正确性3、準備
給類的靜态變量配置設定記憶體,并賦予預設值4、解析
将符号引用替換為直接引用,該階段會把一些靜态方法(符号引用,比如main()方法)替換為指向資料
所存記憶體的指針或句柄等(直接引用),這是所謂的靜态連結過程(類加載期間完成),動态連結是在程
序運作期間完成的将符号引用替換為直接引用,下節課會講到動态連結5、初始化
對類的靜态變量初始化為指定的值,執行靜态代碼塊
![]()
徹底剖析JVM類加載機制(一) 類被加載到方法區中後主要包含 運作時常量池、類型資訊、字段資訊、方法資訊、類加載器的引用、對應class執行個體的引用等資訊。
類加載器的引用:這個類到類加載器執行個體的引用
對應class執行個體的引用:類加載器在加載類資訊放到方法區中後,會建立一個對應的Class 類型的對象執行個體放到堆(Heap)中, 作為開發人員通路方法區中類定義的入口和切入點。
package com.zhz.jvm;/**
* @author :zhz
* @date :Created in 2020/12/31
* @version: V1.0
* @slogan: 天下風雲出我輩,一入代碼歲月催
* @description: 驗證部分加載
**/public class TestDynamicLoad {static {System.out.println("--------load TestDynamicLoad--------");}public static void main(String[] args) {new A1();System.out.println("--------------------load test--------------------");B1 b1 = null;///B不會加載,除非這裡執行 new B()}}class A1 {static {System.out.println("*************load A************");}public A1() {System.out.println("*************initial A************");}}class B1{static {System.out.println("*************load B************");}
public B1() {System.out.println("*************initial B************");}}
運作結果:
結論:主類在運作過程中如果使用到其它類,會逐漸加載這些類。jar包或war包裡的類
Java裡有如下幾種類加載器
1、導類加載器:負責加載支撐JVM運作的位于JRE的lib目錄下的核心類庫,比如rt.jar、charsets.jar等
2、擴充類加載器:負責加載支撐JVM運作的位于JRE的lib目錄下的ext擴充目錄中的JAR類包
3、負責加載ClassPath路徑下的類包,主要就是加載你自己寫的那些類
4、負責加載使用者自定義路徑下的類包
類加載器示例:
package com.zhz.jvm;import com.sun.crypto.provider.DESKeyFactory;import sun.misc.Launcher;import java.net.URL;/**
* @author :zhz
* @date :Created in 2020/12/31
* @version: V1.0
* @slogan: 天下風雲出我輩,一入代碼歲月催
* @description:
**/public class TestJDKClassLoader {public static <RL> void main(String[] args) {System.out.println(String.class.getClassLoader());System.out.println(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"));}}
"E:\Program Files\Java\jdk1.8.0_181\bin\java.exe" -agentlib:jdwp=transport=dt_socket,address=127.0.0.1:52473,suspend=y,server=n -Dvisualvm.id=847811161252100 -javaagent:C:\Users\zhz\.IntelliJIdea2019.3\system\captureAgent\debugger-agent.jar -Dfile.encoding=UTF-8 -classpath "E:\Program Files\Java\jdk1.8.0_181\jre\lib\charsets.jar;E:\Program Files\Java\jdk1.8.0_181\jre\lib\deploy.jar;E:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\access-bridge-64.jar;E:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\cldrdata.jar;E:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\dnsns.jar;E:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\jaccess.jar;E:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\jfxrt.jar;E:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\localedata.jar;E:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\nashorn.jar;E:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\sunec.jar;E:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\sunjce_provider.jar;E:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\sunmscapi.jar;E:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\sunpkcs11.jar;E:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\zipfs.jar;E:\Program Files\Java\jdk1.8.0_181\jre\lib\javaws.jar;E:\Program Files\Java\jdk1.8.0_181\jre\lib\jce.jar;E:\Program Files\Java\jdk1.8.0_181\jre\lib\jfr.jar;E:\Program Files\Java\jdk1.8.0_181\jre\lib\jfxswt.jar;E:\Program Files\Java\jdk1.8.0_181\jre\lib\jsse.jar;E:\Program Files\Java\jdk1.8.0_181\jre\lib\management-agent.jar;E:\Program Files\Java\jdk1.8.0_181\jre\lib\plugin.jar;E:\Program Files\Java\jdk1.8.0_181\jre\lib\resources.jar;E:\Program Files\Java\jdk1.8.0_181\jre\lib\rt.jar;E:\IdeaProjects\interview\out\production\interview;E:\myeclipse\BookShop\WebRoot\WEB-INF\lib\mysql-connector-java-5.1.30.jar;D:\developer_tools\IDEA\IntelliJ IDEA 2019.3.2\lib\idea_rt.jar" com.zhz.jvm.TestJDKClassLoader
Connected to the target VM, address: '127.0.0.1:52473', transport: 'socket'null
sun.misc.Launcher$ExtClassLoader
sun.misc.Launcher$AppClassLoader
the bootstrapLoader : null
the extClassloader : sun.misc.Launcher$ExtClassLoader@2c8d66b2the appClassLoader : sun.misc.Launcher$AppClassLoader@18b4aac2bootstrapLoader加載以下檔案:
file:/E:/Program%20Files/Java/jdk1.8.0_181/jre/lib/resources.jar
file:/E:/Program%20Files/Java/jdk1.8.0_181/jre/lib/rt.jar
file:/E:/Program%20Files/Java/jdk1.8.0_181/jre/lib/sunrsasign.jar
file:/E:/Program%20Files/Java/jdk1.8.0_181/jre/lib/jsse.jar
file:/E:/Program%20Files/Java/jdk1.8.0_181/jre/lib/jce.jar
file:/E:/Program%20Files/Java/jdk1.8.0_181/jre/lib/charsets.jar
file:/E:/Program%20Files/Java/jdk1.8.0_181/jre/lib/jfr.jar
file:/E:/Program%20Files/Java/jdk1.8.0_181/jre/classes
extClassloader加載以下檔案:
E:\Program Files\Java\jdk1.8.0_181\jre\lib\ext;C:\WINDOWS\Sun\Java\lib\ext
appClassLoader加載以下檔案:
E:\Program Files\Java\jdk1.8.0_181\jre\lib\charsets.jar;E:\Program Files\Java\jdk1.8.0_181\jre\lib\deploy.jar;E:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\access-bridge-64.jar;E:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\cldrdata.jar;E:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\dnsns.jar;E:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\jaccess.jar;E:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\jfxrt.jar;E:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\localedata.jar;E:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\nashorn.jar;E:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\sunec.jar;E:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\sunjce_provider.jar;E:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\sunmscapi.jar;E:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\sunpkcs11.jar;E:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\zipfs.jar;E:\Program Files\Java\jdk1.8.0_181\jre\lib\javaws.jar;E:\Program Files\Java\jdk1.8.0_181\jre\lib\jce.jar;E:\Program Files\Java\jdk1.8.0_181\jre\lib\jfr.jar;E:\Program Files\Java\jdk1.8.0_181\jre\lib\jfxswt.jar;E:\Program Files\Java\jdk1.8.0_181\jre\lib\jsse.jar;E:\Program Files\Java\jdk1.8.0_181\jre\lib\management-agent.jar;E:\Program Files\Java\jdk1.8.0_181\jre\lib\plugin.jar;E:\Program Files\Java\jdk1.8.0_181\jre\lib\resources.jar;E:\Program Files\Java\jdk1.8.0_181\jre\lib\rt.jar;E:\IdeaProjects\interview\out\production\interview;E:\myeclipse\BookShop\WebRoot\WEB-INF\lib\mysql-connector-java-5.1.30.jar;D:\developer_tools\IDEA\IntelliJ IDEA 2019.3.2\lib\idea_rt.jar;C:\Users\zhz\.IntelliJIdea2019.3\system\captureAgent\debugger-agent.jar
Disconnected from the target VM, address: '127.0.0.1:52473', transport: 'socket'Process finished with exit code 0
參見類運作加載全過程圖可知其中會建立JVM啟動器執行個體sun.misc.Launcher。sun.misc.Launcher初始化使用了單例模式設計,保證一個JVM虛拟機内隻有一個sun.misc.Launcher執行個體。在Launcher構造方法内部,其建立了兩個類加載器,分别是sun.misc.Launcher.ExtClassLoader(擴充類加載器)和sun.misc.Launcher.AppClassLoader(應用類加載器)。
JVM預設使用launcher的getClassLoader()方法傳回的類加載器AppClassLoader的執行個體來加載我們的應用程式。
//Launcher的構造方法
public Launcher() {Launcher.ExtClassLoader var1;try { //構造擴充類加載器,在構造的過程中将其父加載器設定為nullvar1 = Launcher.ExtClassLoader.getExtClassLoader();} catch (IOException var10) {throw new InternalError("Could not create extension class loader", var10);}try { //構造應用類加載器,在構造的過程中将其父加載器設定為ExtClassLoader,//Launcher的loader屬性值是AppClassLoader,我們一般都是用這個類加載器來加載我們自己寫的應用程式this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);} catch (IOException var9) {throw new InternalError("Could not create application class loader", var9);}Thread.currentThread().setContextClassLoader(this.loader);String var2 = System.getProperty("java.security.manager");if (var2 != null) {SecurityManager var3 = null;if (!"".equals(var2) && !"default".equals(var2)) {try {var3 = (SecurityManager)this.loader.loadClass(var2).newInstance();} catch (IllegalAccessException var5) {} catch (InstantiationException var6) {} catch (ClassNotFoundException var7) {} catch (ClassCastException var8) {}} else {var3 = new SecurityManager();}if (var3 == null) {throw new InternalError("Could not create SecurityManager: " + var2);}System.setSecurityManager(var3);}
JVM類加載器是有親子層級結構的,如下圖:
這裡類加載其實就有一個雙親委派機制,加載某個類時會先委托父加載器尋找目标類,找不到再委托上層父加載器加載,如果所有父加載器在自己的加載類路徑下都找不到目标類,則在自己的類加載路徑中查找并載入目标類。
比如我們的TestMath類,最先會找應用程式類加載器加載,應用程式類加載器會先委托擴充類加載器加載,擴充類加載器再委托引導類加載器,頂層引導類加載器在自己的類加載路徑裡找了半天沒找到TestMath類,則向下退回加載Math類的請求,擴充類加載器收到回複就自己加載,在自己的類加載路徑裡找了半天也沒找到TestMath類,又向下退回TestMath類的加載請求給應用程式類加載器,應用程式類加載器于是在自己的類加載路徑裡找TestMath類,結果找到了就自己加載了。。
雙親委派機制說簡單點就是,先找父親加載,不行再由兒子自己加載
我們來看下應用程式類加載器AppClassLoader加載類的雙親委派機制源碼,AppClassLoader的loadClass方法最終會調用其父類ClassLoader的loadClass方法,該方法的大體邏輯如下:
首先,檢查一下指定名稱的類是否已經加載過,如果加載過了,就不需要再加載,直接傳回。
如果此類沒有加載過,那麼,再判斷一下是否有父加載器;如果有父加載器,則由父加載器加載(即調用parent.loadClass(name, false);).或者是調用bootstrap類加載器來加載。
如果父加載器及bootstrap類加載器都沒有找到指定的類,那麼調用目前類加載器的findClass方法來完成類加載。
//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) {// If still not found, then invoke findClass in order// to find the class.long t1 = System.nanoTime();//都會調用URLClassLoader的findClass方法在加載器的類路徑裡查找并加載該類c = findClass(name);// this is the defining class loader; record the statssun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);sun.misc.PerfCounter.getFindClasses().increment();}}if (resolve) { //不會執行resolveClass(c);}return c;}}
1、沙箱安全機制:自己寫的java.lang.String.class類不會被加載,這樣便可以防止核心API庫被随意篡改
2、避免類的重複加載:當父親已經加載了該類時,就沒有必要子ClassLoader再加載一次,保證被加載類的唯一性
例如:
package java.lang;public class String {public static void main(String[] args) {System.out.println("**************My String Class**************");}}運作結果:
錯誤: 在類 java.lang.String 中找不到 main 方法, 請将 main 方法定義為:
public static void main(String[] args)否則 JavaFX 應用程式類必須擴充javafx.application.Application
“全盤負責”是指當一個ClassLoder裝載一個類時,除非顯示的使用另外一個ClassLoder,該類所依賴及引用的類也由這個ClassLoder載入。
自定義類加載器隻需要繼承 java.lang.ClassLoader 類,該類有兩個核心方法,一個是loadClass(String, boolean),實作了雙親委派機制,還有一個方法是findClass,預設實作是空方法,是以我們自定義類加載器主要是重寫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将一個位元組數組轉為Class對象,這個位元組數組是class檔案讀取後最終的位元組數組。return defineClass(name, data, 0, data.length);} catch (Exception e) {e.printStackTrace();throw new ClassNotFoundException();}}}public static void main(String args[]) throws Exception {//初始化自定義類加載器,會先初始化父類ClassLoader,其中會把自定義類加載器的父加載器設定為應用程式類加載器AppClassLoaderMyClassLoader classLoader = new MyClassLoader("D:/test");//D盤建立 test/com/zhz/jvm 幾級目錄,将User類的複制類User1.class丢入該目錄Class clazz = classLoader.loadClass("com.zhz.jvm.User1");Object obj = clazz.newInstance();Method method = clazz.getDeclaredMethod("sout", null);method.invoke(obj, null);System.out.println(clazz.getClassLoader().getClass().getName());}}運作結果:=======自己的加載器加載類調用方法=======com.zhz.jvm.MyClassLoaderTest$MyClassLoader
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);return defineClass(name, data, 0, data.length);} catch (Exception e) {e.printStackTrace();throw new ClassNotFoundException();}}/**
* 重寫類加載方法,實作自己的加載邏輯,不委派給雙親加載
* @param name
* @param resolve
* @return
* @throws ClassNotFoundException
*/protected Class<?> loadClass(String name, boolean resolve)throws ClassNotFoundException {synchronized (getClassLoadingLock(name)) {// First, check if the class has already been loadedClass<?> c = findLoadedClass(name);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 statssun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);sun.misc.PerfCounter.getFindClasses().increment();}if (resolve) {resolveClass(c);}return c;}}}public static void main(String args[]) throws Exception {MyClassLoader classLoader = new MyClassLoader("D:/test");//嘗試用自己改寫類加載機制去加載自己寫的java.lang.String.classClass clazz = classLoader.loadClass("java.lang.String");Object obj = clazz.newInstance();Method method= clazz.getDeclaredMethod("sout", null);method.invoke(obj, null);System.out.println(clazz.getClassLoader().getClass().getName());}}運作結果:
java.lang.SecurityException: Prohibited package name: java.lang
at java.lang.ClassLoader.preDefineClass(ClassLoader.java:659)
at java.lang.ClassLoader.defineClass(ClassLoader.java:758)