這篇文章主要講解了“怎么掌握類加載器的相關(guān)知識(shí)點(diǎn)”,文中的講解內(nèi)容簡(jiǎn)單清晰,易于學(xué)習(xí)與理解,下面請(qǐng)大家跟著小編的思路慢慢深入,一起來(lái)研究和學(xué)習(xí)“怎么掌握類加載器的相關(guān)知識(shí)點(diǎn)”吧!
為企業(yè)提供做網(wǎng)站、成都網(wǎng)站制作、網(wǎng)站優(yōu)化、全網(wǎng)營(yíng)銷推廣、競(jìng)價(jià)托管、品牌運(yùn)營(yíng)等營(yíng)銷獲客服務(wù)。創(chuàng)新互聯(lián)擁有網(wǎng)絡(luò)營(yíng)銷運(yùn)營(yíng)團(tuán)隊(duì),以豐富的互聯(lián)網(wǎng)營(yíng)銷經(jīng)驗(yàn)助力企業(yè)精準(zhǔn)獲客,真正落地解決中小企業(yè)營(yíng)銷獲客難題,做到“讓獲客更簡(jiǎn)單”。自創(chuàng)立至今,成功用技術(shù)實(shí)力解決了企業(yè)“網(wǎng)站建設(shè)、網(wǎng)絡(luò)品牌塑造、網(wǎng)絡(luò)營(yíng)銷”三大難題,同時(shí)降低了營(yíng)銷成本,提高了有效客戶轉(zhuǎn)化率,獲得了眾多企業(yè)客戶的高度認(rèn)可!
如果自己想手寫一個(gè)Java虛擬機(jī)的話,主要考慮哪些結(jié)構(gòu)呢?
類加載器
執(zhí)行引擎
完整框圖:
類加載器子系統(tǒng)作用
類加載器子系統(tǒng)負(fù)責(zé)從文件系統(tǒng)或者網(wǎng)絡(luò)中加載Class文件,class文件在文件開(kāi)頭有特定的文件標(biāo)識(shí)。
ClassLoader只負(fù)責(zé)class文件的加載,至于它是否可以運(yùn)行,則由Execution Engine決定。
加載的類信息存放于一塊稱為方法區(qū)的內(nèi)存空間。除了類的信息外,方法區(qū)中還會(huì)存放運(yùn)行時(shí)常量池信息,可能還包括字符串字面量和數(shù)字常量(這部分常量信息是Class文件中常量池部分的內(nèi)存映射)
class --> Java.lang.Class
class file存在于本地硬盤上,可以理解為設(shè)計(jì)師畫在紙上的模板,而最終這個(gè)模板在執(zhí)行的時(shí)候是要加載到JVM當(dāng)中來(lái)根據(jù)這個(gè)文件實(shí)例化出n個(gè)一模一樣的實(shí)例。
class file加載到JVM中,被稱為DNA元數(shù)據(jù)模板,放在方法區(qū)。
在.class文件–>JVM–>最終成為元數(shù)據(jù)模板,此過(guò)程就要一個(gè)運(yùn)輸工具(類裝載器Class Loader),扮演一個(gè)快遞員的角色。
看代碼
public class HelloLoader { public static void main(String[] args) { System.out.println("謝謝ClassLoader加載我...."); System.out.println("你的大恩大德,我下輩子再報(bào)!"); } }
它的加載過(guò)程是怎么樣的呢?
執(zhí)行 main() 方法(靜態(tài)方法)就需要先加載承載類 HelloLoader
加載成功,則進(jìn)行鏈接、初始化等操作,完成后調(diào)用 HelloLoader 類中的靜態(tài)方法 main
加載失敗則拋出異常
完整的流程圖如下所示: *加載 --> 鏈接(驗(yàn)證 --> 準(zhǔn)備 --> 解析) --> 初始化
加載流程
通過(guò)一個(gè)類的全限定名獲取定義此類的二進(jìn)制字節(jié)流
將這個(gè)字節(jié)流所代表的靜態(tài)存儲(chǔ)結(jié)構(gòu)轉(zhuǎn)化為 方法區(qū)的運(yùn)行時(shí)數(shù)據(jù)結(jié)構(gòu)
在內(nèi)存中生成一個(gè)代表這個(gè)類的java.lang.Class對(duì)象,作為方法區(qū)這個(gè)類的各種數(shù)據(jù)的訪問(wèn)入口
加載class文件的方式
從本地系統(tǒng)中直接加載
通過(guò)網(wǎng)絡(luò)獲取,典型場(chǎng)景:Web Applet
從zip壓縮包中讀取,成為日后jar、war格式的基礎(chǔ)
運(yùn)行時(shí)計(jì)算生成,使用最多的是:動(dòng)態(tài)代理技術(shù)
由其他文件生成,典型場(chǎng)景:JSP應(yīng)用從專有數(shù)據(jù)庫(kù)中提取.class文件,比較少見(jiàn)
從加密文件中獲取,典型的防Class文件被反編譯的保護(hù)措施
*鏈接分為三個(gè)子階段:驗(yàn)證 --> 準(zhǔn)備 --> 解析
驗(yàn)證
目的在于確保Class文件的字節(jié)流中包含信息符合當(dāng)前虛擬機(jī)要求,保證被加載類的正確性,不會(huì)危害虛擬機(jī)自身安全
主要包括四種驗(yàn)證,文件格式驗(yàn)證,元數(shù)據(jù)驗(yàn)證,字節(jié)碼驗(yàn)證,符號(hào)引用驗(yàn)證。
舉例
使用 BinaryViewer 查看字節(jié)碼文件,其開(kāi)頭均為 CAFE BABE ,如果出現(xiàn)不合法的字節(jié)碼文件,那么將會(huì)驗(yàn)證不通過(guò)
準(zhǔn)備
為類變量分配內(nèi)存并且設(shè)置該類變量的默認(rèn)初始值,即零值
這里不包含用final修飾的static,因?yàn)閒inal在編譯的時(shí)候就會(huì)分配好了默認(rèn)值,準(zhǔn)備階段會(huì)顯式初始化
注意:這里不會(huì)為實(shí)例變量分配初始化,類變量會(huì)分配在方法區(qū)中,而實(shí)例變量是會(huì)隨著對(duì)象一起分配到Java堆中
舉例
代碼:變量a在準(zhǔn)備階段會(huì)賦初始值,但不是1,而是0,在初始化階段會(huì)被賦值為 1
public class HelloApp { private static int a = 1; public static void main(String[] args) { System.out.println(a); } }
解析
將常量池內(nèi)的符號(hào)引用轉(zhuǎn)換為直接引用的過(guò)程
事實(shí)上,解析操作往往會(huì)伴隨著JVM在執(zhí)行完初始化之后再執(zhí)行
符號(hào)引用就是一組符號(hào)來(lái)描述所引用的目標(biāo)。符號(hào)引用的字面量形式明確定義在《java虛擬機(jī)規(guī)范》的class文件格式中。直接引用就是直接指向目標(biāo)的指針、相對(duì)偏移量或一個(gè)間接定位到目標(biāo)的句柄
解析動(dòng)作主要針對(duì)類或接口、字段、類方法、接口方法、方法類型等。對(duì)應(yīng)常量池中的CONSTANT Class info、CONSTANT Fieldref info、CONSTANT Methodref info等
符號(hào)引用
反編譯 class 文件后可以查看符號(hào)引用
初始化階段
初始化階段就是執(zhí)行類構(gòu)造器方法
的過(guò)程
此方法不需定義,是javac編譯器自動(dòng)收集類中的所有類變量的賦值動(dòng)作和靜態(tài)代碼塊中的語(yǔ)句合并而來(lái)。也就是說(shuō), 當(dāng)我們代碼中包含static變量的時(shí)候,就會(huì)有clinit方法
**
方法中的指令按語(yǔ)句在源文件中出現(xiàn)的順序執(zhí)行**
不同于類的構(gòu)造器。(關(guān)聯(lián):構(gòu)造器是虛擬機(jī)視角下的
)
若該類具有父類,JVM會(huì)保證子類的
執(zhí)行前,父類的
已經(jīng)執(zhí)行完畢
虛擬機(jī)必須保證一個(gè)類的
方法在多線程下被同步加鎖
IDEA 中安裝 JClassLib 插件
在 IDEA 中安裝 JClassLib 插件后,重啟 IDEA 生效
選中對(duì)應(yīng)的 Java 類文件,注意:不是字節(jié)碼文件~!
點(diǎn)擊【View --> Show Bytecode With jclasslib】即可查看反編譯后的代碼
當(dāng)我們代碼中包含static變量的時(shí)候,就會(huì)有clinit方法
示例 1:無(wú) static 變量
代碼
public class ClinitTest { private int a = 1; public static void main(String[] args) { int b = 2; } }
并沒(méi)有生成 clinit 方法
示例 2:有 static 變量
代碼
public class ClinitTest { private int a = 1; private static int c = 3; public static void main(String[] args) { int b = 2; } }
在 clinit 方法中初始化靜態(tài)變量的值為 3
構(gòu)造器方法中指令按語(yǔ)句在源文件中出現(xiàn)的順序執(zhí)行
示例 1
代碼:
public class ClassInitTest { private static int num = 1; private static int number = 10; static { num = 2; number = 20; System.out.println(num); } public static void main(String[] args) { System.out.println(ClassInitTest.num); System.out.println(ClassInitTest.number); } }
靜態(tài)變量 number 的值變化過(guò)程如下
準(zhǔn)備階段時(shí):0
執(zhí)行靜態(tài)變量初始化:10
執(zhí)行靜態(tài)代碼塊:20
示例 1
代碼
public class ClassInitTest { private static int num = 1; static{ num = 2; number = 20; System.out.println(num); } private static int number = 10; public static void main(String[] args) { System.out.println(ClassInitTest.num); System.out.println(ClassInitTest.number); } }
靜態(tài)變量 number 的值變化過(guò)程如下
準(zhǔn)備階段時(shí):0
執(zhí)行靜態(tài)代碼塊:20
執(zhí)行靜態(tài)變量初始化:10
構(gòu)造器是虛擬機(jī)視角下的
()
代碼
public class ClinitTest { private int a = 1; private static int c = 3; public static void main(String[] args) { int b = 2; } public ClinitTest(){ a = 10; int d = 20; } }
在構(gòu)造器中:
先將類變量 a 賦值為 10
再將局部變量賦值為 20
若該類具有父類,JVM會(huì)保證子類的
執(zhí)行前,父類的
() 已經(jīng)執(zhí)行完畢
()
代碼
public class ClinitTest1 { static class Father{ public static int A = 1; static{ A = 2; } } static class Son extends Father{ public static int B = A; } public static void main(String[] args) { System.out.println(Son.B); } }
如上代碼,加載流程如下:
首先,執(zhí)行 main() 方法需要加載 ClinitTest1 類
獲取 Son.B 靜態(tài)變量,需要加載 Son 類
Son 類的父類是 Father 類,所以需要先執(zhí)行 Father 類的加載,再執(zhí)行 Son 類的加載
虛擬機(jī)必須保證一個(gè)類的
方法在多線程下被同步加鎖
()
代碼
public class DeadThreadTest { public static void main(String[] args) { Runnable r = () -> { System.out.println(Thread.currentThread().getName() + "開(kāi)始"); DeadThread dead = new DeadThread(); System.out.println(Thread.currentThread().getName() + "結(jié)束"); }; Thread t1 = new Thread(r, "線程1"); Thread t2 = new Thread(r, "線程2"); t1.start(); t2.start(); } } class DeadThread { static { if (true) { System.out.println(Thread.currentThread().getName() + "初始化當(dāng)前類"); while (true) { } } } }
程序卡死,分析原因:
兩個(gè)線程同時(shí)去加載 DeadThread 類,而 DeadThread 類中靜態(tài)代碼塊中有一處死循環(huán)
先加載 DeadThread 類的線程搶到了同步鎖,然后在類的靜態(tài)代碼塊中執(zhí)行死循環(huán),而另一個(gè)線程在等待同步鎖的釋放
所以無(wú)論哪個(gè)線程先執(zhí)行 DeadThread 類的加載,另外一個(gè)類也不會(huì)繼續(xù)執(zhí)行
類加載器的分類
JVM支持兩種類型的類加載器 。分別為引導(dǎo)類加載器(Bootstrap ClassLoader)和自定義類加載器(User-Defined ClassLoader)
從概念上來(lái)講,自定義類加載器一般指的是程序中由開(kāi)發(fā)人員自定義的一類類加載器,但是Java虛擬機(jī)規(guī)范卻沒(méi)有這么定義,而是 將所有派生于抽象類ClassLoader的類加載器都劃分為自定義類加載器
無(wú)論類加載器的類型如何劃分,在程序中我們最常見(jiàn)的類加載器始終只有3個(gè),如下所示
這里的四者之間是包含關(guān)系,不是上層和下層,也不是子父類的繼承關(guān)系。
為什么 ExtClassLoader 和 AppClassLoader 都屬于自定義加載器
規(guī)范定義:所有派生于抽象類ClassLoader的類加載器都劃分為自定義類加載器
ExtClassLoader 繼承樹(shù)
AppClassLoader 繼承樹(shù)
代碼:
我們嘗試獲取引導(dǎo)類加載器,獲取到的值為 null ,這并不代表引導(dǎo)類加載器不存在, 因?yàn)橐龑?dǎo)類加載器右 C/C++ 語(yǔ)言,我們獲取不到
兩次獲取系統(tǒng)類加載器的值都相同:sun.misc.Launcher$AppClassLoader@18b4aac2 ,這說(shuō)明 *系統(tǒng)類加載器是全局唯一的
public class ClassLoaderTest { public static void main(String[] args) { ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader(); System.out.println(systemClassLoader); ClassLoader extClassLoader = systemClassLoader.getParent(); System.out.println(extClassLoader); ClassLoader bootstrapClassLoader = extClassLoader.getParent(); System.out.println(bootstrapClassLoader); ClassLoader classLoader = ClassLoaderTest.class.getClassLoader(); System.out.println(classLoader); ClassLoader classLoader1 = String.class.getClassLoader(); System.out.println(classLoader1); } }
啟動(dòng)類加載器(引導(dǎo)類加載器,Bootstrap ClassLoader)
這個(gè)類加載使用C/C++語(yǔ)言實(shí)現(xiàn)的,嵌套在JVM內(nèi)部
它用來(lái)加載Java的核心庫(kù)(JAVA_HOME/jre/lib/rt.jar、resources.jar或sun.boot.class.path路徑下的內(nèi)容),用于提供JVM自身需要的類
并不繼承自java.lang.ClassLoader,沒(méi)有父加載器
加載擴(kuò)展類和應(yīng)用程序類加載器,并作為他們的父類加載器(當(dāng)他倆的爹)
出于安全考慮,Bootstrap啟動(dòng)類加載器只加載包名為java、javax、sun等開(kāi)頭的類
擴(kuò)展類加載器(Extension ClassLoader)
Java語(yǔ)言編寫,由sun.misc.Launcher$ExtClassLoader實(shí)現(xiàn)
派生于ClassLoader類
父類加載器為啟動(dòng)類加載器
從java.ext.dirs系統(tǒng)屬性所指定的目錄中加載類庫(kù),或從JDK的安裝目錄的jre/lib/ext子目錄(擴(kuò)展目錄)下加載類庫(kù)。如果用戶創(chuàng)建的JAR放在此目錄下,也會(huì)自動(dòng)由擴(kuò)展類加載器加載
應(yīng)用程序類加載器(系統(tǒng)類加載器,AppClassLoader)
Java語(yǔ)言編寫,由sun.misc.LaunchersAppClassLoader實(shí)現(xiàn)
派生于ClassLoader類
父類加載器為擴(kuò)展類加載器
它負(fù)責(zé)加載環(huán)境變量classpath或系統(tǒng)屬性java.class.path指定路徑下的類庫(kù)
該類加載是程序中默認(rèn)的類加載器,一般來(lái)說(shuō),Java應(yīng)用的類都是由它來(lái)完成加載
通過(guò)classLoader.getSystemclassLoader()方法可以獲取到該類加載器
代碼舉例說(shuō)明
代碼
public class ClassLoaderTest1 { public static void main(String[] args) { System.out.println("**********啟動(dòng)類加載器**************"); URL[] urLs = sun.misc.Launcher.getBootstrapClassPath().getURLs(); for (URL element : urLs) { System.out.println(element.toExternalForm()); } ClassLoader classLoader = Provider.class.getClassLoader(); System.out.println(classLoader); System.out.println("***********擴(kuò)展類加載器*************"); String extDirs = System.getProperty("java.ext.dirs"); for (String path : extDirs.split(";")) { System.out.println(path); } ClassLoader classLoader1 = CurveDB.class.getClassLoader(); System.out.println(classLoader1); } }
System.out.println(classLoader); 輸出 null ,再次證明我們無(wú)法獲取到啟動(dòng)類加載器
**********启动类加载器************** file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/resources.jar file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/rt.jar file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/sunrsasign.jar file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/jsse.jar file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/jce.jar file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/charsets.jar file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/jfr.jar file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/classes null ***********扩展类加载器************* C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext C:\WINDOWS\Sun\Java\lib\ext sun.misc.Launcher$ExtClassLoader@7ea987ac
為什么需要自定義類加載器?
在Java的日常應(yīng)用程序開(kāi)發(fā)中,類的加載幾乎是由上述3種類加載器相互配合執(zhí)行的,在必要時(shí),我們還可以自定義類加載器,來(lái)定制類的加載方式。那為什么還需要自定義類加載器?
隔離加載類
修改類加載的方式
擴(kuò)展加載源
防止源碼泄漏
如何自定義類加載器?
開(kāi)發(fā)人員可以通過(guò)繼承抽象類java.lang.ClassLoader類的方式,實(shí)現(xiàn)自己的類加載器,以滿足一些特殊的需求
在JDK1.2之前,在自定義類加載器時(shí),總會(huì)去繼承ClassLoader類并重寫loadClass()方法,從而實(shí)現(xiàn)自定義的類加載類,但是在JDK1.2之后已不再建議用戶去覆蓋loadClass()方法,而是建議把自定義的類加載邏輯寫在findclass()方法中
在編寫自定義類加載器時(shí),如果沒(méi)有太過(guò)于復(fù)雜的需求,可以直接繼承URIClassLoader類,這樣就可以避免自己去編寫findclass()方法及其獲取字節(jié)碼流的方式,使自定義類加載器編寫更加簡(jiǎn)潔。
代碼示例
public class CustomClassLoader extends ClassLoader { @Override protected Class> findClass(String name) throws ClassNotFoundException { try { byte[] result = getClassFromCustomPath(name); if (result == null) { throw new FileNotFoundException(); } else { return defineClass(name, result, 0, result.length); } } catch (FileNotFoundException e) { e.printStackTrace(); } throw new ClassNotFoundException(name); } private byte[] getClassFromCustomPath(String name) { return null; } public static void main(String[] args) { CustomClassLoader customClassLoader = new CustomClassLoader(); try { Class> clazz = Class.forName("One", true, customClassLoader); Object obj = clazz.newInstance(); System.out.println(obj.getClass().getClassLoader()); } catch (Exception e) { e.printStackTrace(); } } }
ClassLoader 類介紹
ClassLoader類,它是一個(gè)抽象類,其后所有的類加載器都繼承自ClassLoader(不包括啟動(dòng)類加載器)
sun.misc.Launcher 它是一個(gè)java虛擬機(jī)的入口應(yīng)用
獲取 ClassLoader 途徑
獲取途徑:
代碼示例:
public class ClassLoaderTest2 { public static void main(String[] args) { try { ClassLoader classLoader = Class.forName("java.lang.String").getClassLoader(); System.out.println(classLoader); ClassLoader classLoader1 = Thread.currentThread().getContextClassLoader(); System.out.println(classLoader1); ClassLoader classLoader2 = ClassLoader.getSystemClassLoader(); System.out.println(classLoader2); } catch (ClassNotFoundException e) { e.printStackTrace(); } } } null sun.misc.Launcher$AppClassLoader@18b4aac2 sun.misc.Launcher$AppClassLoader@18b4aac2
雙親委派機(jī)制的原理
Java虛擬機(jī)對(duì)class文件采用的是按需加載的方式,也就是說(shuō)當(dāng)需要使用該類時(shí)才會(huì)將它的class文件加載到內(nèi)存生成class對(duì)象。而且 加載某個(gè)類的class文件時(shí),Java虛擬機(jī)采用的是雙親委派模式,即把請(qǐng)求交由父類處理,它是一種任務(wù)委派模式
如果一個(gè)類加載器收到了類加載請(qǐng)求,它并不會(huì)自己先去加載,而是把這個(gè)請(qǐng)求委托給父類的加載器去執(zhí)行;
如果父類加載器還存在其父類加載器,則進(jìn)一步向上委托,依次遞歸,請(qǐng)求最終將到達(dá)頂層的啟動(dòng)類加載器;
如果父類加載器可以完成類加載任務(wù),就成功返回,倘若父類加載器無(wú)法完成此加載任務(wù),子加載器才會(huì)嘗試自己去加載,這就是雙親委派模式。
父類加載器一層一層往下分配任務(wù),如果子類加載器能加載,則加載此類,如果將加載任務(wù)分配至系統(tǒng)類加載器也無(wú)法加載此類,則拋出異常
代碼示例
舉例 1 :
代碼:我們自己建立一個(gè) java.lang.String 類,寫上 static 代碼塊
package java.lang; public class String { static{ System.out.println("我是自定義的String類的靜態(tài)代碼塊"); } }
在另外的程序中加載 String 類,看看加載的 String 類是 JDK 自帶的 String 類,還是我們自己編寫的 String 類
public class StringTest { public static void main(String[] args) { java.lang.String str = new java.lang.String(); System.out.println("hello,atguigu.com"); StringTest test = new StringTest(); System.out.println(test.getClass().getClassLoader()); } }
程序并沒(méi)有輸出我們靜態(tài)代碼塊中的內(nèi)容,可見(jiàn)仍然加載的是 JDK 自帶的 String 類
舉例 2 :
代碼:在我們自己的 String 類中整個(gè) main() 方法
package java.lang; public class String { static{ System.out.println("我是自定義的String類的靜態(tài)代碼塊"); } public static void main(String[] args) { System.out.println("hello,String"); } }
由于雙親委派機(jī)制找到的是 JDK 自帶的 String 類,在那個(gè) String 類中并沒(méi)有 main() 方法
舉例 3 :
代碼:在 java.lang 包下整個(gè) ShkStart 類
package java.lang; public class ShkStart { public static void main(String[] args) { System.out.println("hello!"); } }
出于保護(hù)機(jī)制,java.lang 包下不允許我們自定義類
舉例 4 :
當(dāng)我們加載jdbc.jar 用于實(shí)現(xiàn)數(shù)據(jù)庫(kù)連接的時(shí)候
首先我們需要知道的是 jdbc.jar是基于SPI接口進(jìn)行實(shí)現(xiàn)的
所以在加載的時(shí)候,會(huì)進(jìn)行雙親委派,最終從根加載器中加載 SPI核心類,然后再加載SPI接口類
接著在進(jìn)行反向委托,通過(guò)線程上下文類加載器進(jìn)行實(shí)現(xiàn)類 jdbc.jar的加載。
雙親委派機(jī)制的優(yōu)勢(shì)
通過(guò)上面的例子,我們可以知道,雙親機(jī)制可以
避免類的重復(fù)加載
保護(hù)程序安全,防止核心API被隨意篡改
自定義類:java.lang.String 沒(méi)有屌用
自定義類:java.lang.ShkStart(報(bào)錯(cuò):阻止創(chuàng)建 java.lang開(kāi)頭的類)
自定義String類時(shí):在加載自定義String類的時(shí)候會(huì)率先使用引導(dǎo)類加載器加載,而引導(dǎo)類加載器在加載的過(guò)程中會(huì)先加載jdk自帶的文件(rt.jar包中java.lang.String.class),報(bào)錯(cuò)信息說(shuō)沒(méi)有main方法,就是因?yàn)榧虞d的是rt.jar包中的String類。
這樣可以保證對(duì)java核心源代碼的保護(hù),這就是沙箱安全機(jī)制。
如何判斷兩個(gè)class對(duì)象是否相同?
在JVM中表示兩個(gè)class對(duì)象是否為同一個(gè)類存在兩個(gè)必要條件:
類的完整類名必須一致,包括包名
加載這個(gè)類的ClassLoader(指ClassLoader實(shí)例對(duì)象)必須相同
換句話說(shuō),在JVM中,即使這兩個(gè)類對(duì)象(class對(duì)象)來(lái)源同一個(gè)Class文件,被同一個(gè)虛擬機(jī)所加載,但只要加載它們的ClassLoader實(shí)例對(duì)象不同,那么這兩個(gè)類對(duì)象也是不相等的
對(duì)類加載器的引用
JVM必須知道一個(gè)類型是由啟動(dòng)加載器加載的還是由用戶類加載器加載的
如果一個(gè)類型是由用戶類加載器加載的,那么JVM會(huì)將這個(gè)類加載器的一個(gè)引用作為類型信息的一部分保存在方法區(qū)中
當(dāng)解析一個(gè)類型到另一個(gè)類型的引用的時(shí)候,JVM需要保證這兩個(gè)類型的類加載器是相同的
類的主動(dòng)使用和被動(dòng)使用
Java程序?qū)︻惖氖褂梅绞椒譃椋褐鲃?dòng)使用和被動(dòng)使用。主動(dòng)使用,又分為七種情況:
創(chuàng)建類的實(shí)例
訪問(wèn)某個(gè)類或接口的靜態(tài)變量,或者對(duì)該靜態(tài)變量賦值
調(diào)用類的靜態(tài)方法
反射(比如:Class.forName("com.atguigu.Test"))
初始化一個(gè)類的子類
Java虛擬機(jī)啟動(dòng)時(shí)被標(biāo)明為啟動(dòng)類的類
JDK7開(kāi)始提供的動(dòng)態(tài)語(yǔ)言支持:java.lang.invoke.MethodHandle實(shí)例的解析結(jié)果REF_getStatic、REF putStatic、REF_invokeStatic句柄對(duì)應(yīng)的類沒(méi)有初始化,則初始化
感謝各位的閱讀,以上就是“怎么掌握類加載器的相關(guān)知識(shí)點(diǎn)”的內(nèi)容了,經(jīng)過(guò)本文的學(xué)習(xí)后,相信大家對(duì)怎么掌握類加載器的相關(guān)知識(shí)點(diǎn)這一問(wèn)題有了更深刻的體會(huì),具體使用情況還需要大家實(shí)踐驗(yàn)證。這里是創(chuàng)新互聯(lián),小編將為大家推送更多相關(guān)知識(shí)點(diǎn)的文章,歡迎關(guān)注!