在JVM概述中我們已經(jīng)了解了JVM體系結(jié)構(gòu),下文將深入介紹JVM體系結(jié)構(gòu)之一的類(lèi)加載子系統(tǒng)债鸡。
目錄
?1 類(lèi)加載子系統(tǒng)作用
?2 類(lèi)加載器 ClassLoader角色
?3 類(lèi)加載過(guò)程
?4 類(lèi)加載器分類(lèi)
??4.1 ClassLoader的常用方法及獲取方法
??4.2虛擬機(jī)自帶的加載器
??4.3用戶(hù)自定義類(lèi)加載器
?5 雙親委派機(jī)制
??5.1 工作原理
??5.2 沙箱安全機(jī)制
??5.3 雙親委派機(jī)制的優(yōu)勢(shì)
?6 類(lèi)的主動(dòng)使用和被動(dòng)使用
1 類(lèi)加載子系統(tǒng)作用
- 類(lèi)加載子系統(tǒng)負(fù)責(zé)從文件系統(tǒng)或者網(wǎng)絡(luò)中加載Class文件江滨,class文件在文件開(kāi)頭有特定的文件標(biāo)識(shí);
- ClassLoader只負(fù)責(zé)class文件的加載厌均,至于它是否可以運(yùn)行唬滑,則由Execution Engine決定
- 加載的類(lèi)信息存放于一塊成為方法區(qū)的內(nèi)存空間。除了類(lèi)信息之外棺弊,方法區(qū)還會(huì)存放運(yùn)行時(shí)常量池信息晶密,可能還包括字符串字面量和數(shù)字常量(這部分常量信息是Class文件中常量池部分的內(nèi)存映射)
2 類(lèi)加載器 ClassLoader角色
- class file存在本地磁盤(pán)上,可以理解為設(shè)計(jì)師畫(huà)在紙上的模板模她,而最終這個(gè)模板在執(zhí)行時(shí)是要加載到JVM中來(lái)稻艰,根據(jù)這個(gè)文件實(shí)例出n個(gè)一模一樣的實(shí)例。
- class file加載到JVM中侈净,被稱(chēng)為DNA元數(shù)據(jù)模板尊勿,放在方法區(qū)。
- 在.class文件-->JVM-->最終成為元數(shù)據(jù)模板畜侦,此過(guò)程就要一個(gè)運(yùn)輸工具(ClassLoader)元扔,扮演快遞員的角色。
3 類(lèi)加載過(guò)程
①加載
- 通過(guò)一個(gè)類(lèi)的全限定名獲取定義此類(lèi)的二進(jìn)制字節(jié)流旋膳;
- 將這個(gè)字節(jié)流所代表的的靜態(tài)存儲(chǔ)結(jié)構(gòu)轉(zhuǎn)化為方法區(qū)的運(yùn)行時(shí)數(shù)據(jù)澎语;
-
在內(nèi)存中生成一個(gè)代表這個(gè)類(lèi)的java.lang.Class對(duì)象
,作為方法區(qū)這個(gè)類(lèi)的各種數(shù)據(jù)的訪(fǎng)問(wèn)入口。
②驗(yàn)證
- 目的在于確保Class文件的字節(jié)流中包含信息符合當(dāng)前虛擬機(jī)要求咏连,保證被加載類(lèi)的正確性盯孙,不會(huì)危害虛擬機(jī)自身安全鲁森。
- 主要包括四種驗(yàn)證祟滴,文件格式驗(yàn)證(java的class文件都是以
CAFEBABE
開(kāi)頭),源數(shù)據(jù)驗(yàn)證歌溉,字節(jié)碼驗(yàn)證垄懂,符號(hào)引用驗(yàn)證。
③準(zhǔn)備
-
為類(lèi)變量分配內(nèi)存并且設(shè)置該類(lèi)變量的默認(rèn)初始值痛垛,即零值草慧;例如:
這里不包含用final修飾的static,因?yàn)閒inal在編譯的時(shí)候就會(huì)分配了匙头,準(zhǔn)備階段會(huì)顯式初始化漫谷;
不會(huì)為實(shí)例變量分配初始化,類(lèi)變量會(huì)分配在方法去中蹂析,而實(shí)例變量是會(huì)隨著對(duì)象一起分配到j(luò)ava堆中舔示。
④解析
- 將常量池內(nèi)的符號(hào)引用轉(zhuǎn)換為直接引用的過(guò)程。
- 事實(shí)上电抚,解析操作網(wǎng)晚會(huì)伴隨著jvm在執(zhí)行完初始化之后再執(zhí)行
- 符號(hào)引用就是一組符號(hào)來(lái)描述所引用的目標(biāo)惕稻。符號(hào)應(yīng)用的字面量形式明確定義在《java虛擬機(jī)規(guī)范》的class文件格式中。直接引用就是直接指向目標(biāo)的指針蝙叛、相對(duì)偏移量或一個(gè)間接定位到目標(biāo)的句柄
- 解析動(dòng)作主要針對(duì)類(lèi)或接口俺祠、字段、類(lèi)方法借帘、接口方法蜘渣、方法類(lèi)型等。對(duì)應(yīng)常量池中的CONSTANT_Class_info/CONSTANT_Fieldref_info肺然、CONSTANT_Methodref_info等蔫缸。
⑤初始化
-
初始化階段就是執(zhí)行類(lèi)構(gòu)造器方法clinit的過(guò)程。
-
clinit方法不需要定義狰挡,是javac編譯器自動(dòng)收集類(lèi)中的所有類(lèi)變量的賦值動(dòng)作和靜態(tài)代碼塊中的語(yǔ)句合并而來(lái)捂龄。
注:查看字節(jié)碼工具-idea插件jclasslib 構(gòu)造器方法中指令按語(yǔ)句在源文件中出現(xiàn)的順序執(zhí)行
clinit()不同于類(lèi)的構(gòu)造器。(關(guān)聯(lián):構(gòu)造器是虛擬機(jī)視角下的init())
-
若該類(lèi)具有父類(lèi)加叁,jvm會(huì)保證子類(lèi)的clinit()執(zhí)行前倦沧,父類(lèi)的clinit()已經(jīng)執(zhí)行完畢
虛擬機(jī)必須保證一個(gè)類(lèi)的clinit()方法在多線(xiàn)程下被同步加鎖,保證類(lèi)只加載一次。
4 類(lèi)加載器分類(lèi)
- JVM支持兩種類(lèi)型的加載器它匕,分別為引導(dǎo)類(lèi)加載器(BootStrap ClassLoader)和自定義類(lèi)加載器(User-Defined ClassLoader)
-
java虛擬機(jī)規(guī)范義展融,將所有派生于抽象類(lèi)ClassLoader的類(lèi)加載器都劃分為自定義類(lèi)加載器。
4.1 ClassLoader的常用方法及獲取方法
在了解JVM分類(lèi)之前豫柬,先來(lái)了解ClassLoader的常用方法及獲取方法
- ClassLoader類(lèi)告希,它是一個(gè)抽象類(lèi)扑浸,其后所有的類(lèi)加載器都繼承自ClassLoader(不包括啟動(dòng)類(lèi)加載器)
方法名稱(chēng) | 描述 |
---|---|
getParent() | 返回該類(lèi)加載器的超類(lèi)加載器 |
loadClass(String name) | 加載名稱(chēng)為name的類(lèi),返回結(jié)果為java.lang.Class類(lèi)的實(shí)例 |
findClass(String name) | 查找名稱(chēng)為name的類(lèi)燕偶,返回結(jié)果為java.lang.Class類(lèi)的實(shí)例 |
findLoadedClass(String name) | 查找名稱(chēng)為name的已經(jīng)被加載過(guò)的類(lèi)喝噪,返回結(jié)果為java.lang.Class類(lèi)的實(shí)例 |
defineClass(String name,byte[] b,int off,int len) | 把字節(jié)數(shù)組b中的內(nèi)容轉(zhuǎn)換為一個(gè)Java類(lèi) 指么,返回結(jié)果為java.lang.Class類(lèi)的實(shí)例 |
resolveClass(Class<?> c) | 連接指定的一個(gè)java類(lèi) |
- 獲取ClassLoader的途徑
①獲取當(dāng)前類(lèi)的ClassLoader
ClassLoader classLoader = Class.forName("java.lang.String").getClassLoader();
System.out.println(classLoader); //null
②獲取當(dāng)前線(xiàn)程上下文的ClassLoader
ClassLoader classLoader1 = Thread.currentThread().getContextClassLoader();
System.out.println(classLoader1);
③獲取系統(tǒng)的ClassLoader
ClassLoader classLoader2 = ClassLoader.getSystemClassLoader().getParent();
System.out.println(classLoader2);
④獲取調(diào)用者的ClassLoader
-
拓展類(lèi)加載器和系統(tǒng)類(lèi)加載器間接繼承于ClassLoader抽象類(lèi)
4.2虛擬機(jī)自帶的加載器
①啟動(dòng)類(lèi)加載器(引導(dǎo)類(lèi)加載器酝惧,BootStrap ClassLoader)
-
這個(gè)類(lèi)加載使用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自身需要的類(lèi)
不繼承自java.lang.ClassLoader,沒(méi)有父加載器
加載拓展類(lèi)和應(yīng)用程序類(lèi)加載器晚唇,并指定為他們的父加載器
處于安全考慮,BootStrap啟動(dòng)類(lèi)加載器只加載包名為
java盗似、javax哩陕、sun
等開(kāi)頭的類(lèi)
②拓展類(lèi)加載器(Extension ClassLoader)
java語(yǔ)言編寫(xiě) ,由sun.misc.Launcher$ExtClassLoader實(shí)現(xiàn)赫舒。
派生于ClassLoader類(lèi)
-
父類(lèi)加載器為啟動(dòng)類(lèi)加載器
-
從java.ext.dirs系統(tǒng)屬性所指定的目錄中加載類(lèi)庫(kù)悍及,或從JDK的安裝目錄的jre/lib/ext子目錄(擴(kuò)展目錄)下加載類(lèi)庫(kù)。如果用戶(hù)創(chuàng)建的JAR放在此目錄下号阿,也會(huì)由拓展類(lèi)加載器自動(dòng)加載
③應(yīng)用程序類(lèi)加載器(系統(tǒng)類(lèi)加載器并鸵,AppClassLoader)
java語(yǔ)言編寫(xiě), 由sun.misc.Launcher$AppClassLoader實(shí)現(xiàn)扔涧。
派生于ClassLoader類(lèi)
-
父類(lèi)加載器為拓展類(lèi)加載器
它負(fù)責(zé)加載環(huán)境變量classpath或系統(tǒng)屬性 java.class.path指定路徑下的類(lèi)庫(kù)
該類(lèi)加載器是程序中默認(rèn)的類(lèi)加載器园担,一般來(lái)說(shuō),java應(yīng)用的類(lèi)都是由它來(lái)完成加載
通過(guò)ClassLoader#getSystemClassLoader()方法可以獲取到該類(lèi)加載器
上文中提及的父子加載器并非簡(jiǎn)單的繼承關(guān)系而是
雙親委派模式
枯夜,下文第五節(jié)將詳細(xì)說(shuō)明此內(nèi)容弯汰。
4.3用戶(hù)自定義類(lèi)加載器
①為什么要自定義類(lèi)加載器
隔離加載類(lèi)
修改類(lèi)加載的方式
拓展加載源
防止源碼泄漏
②自定義類(lèi)加載器的實(shí)現(xiàn)
參考https://segmentfault.com/a/1190000012925715
5 雙親委派機(jī)制
Java虛擬機(jī)對(duì)class文件采用的是按需加載的方式,也就是說(shuō)當(dāng)需要使用該類(lèi)時(shí)才會(huì)將它的class文件加載到內(nèi)存生成的class對(duì)象湖雹。而且加載某個(gè)類(lèi)的class文件時(shí)咏闪,java虛擬機(jī)采用的是雙親委派模式,即把請(qǐng)求交由父類(lèi)處理摔吏,它是一種任務(wù)委派模式鸽嫂。
5.1 工作原理
- 如果一個(gè)類(lèi)加載器收到了類(lèi)加載請(qǐng)求,它并不會(huì)自己先去加載征讲,而是把這個(gè)請(qǐng)求委托給父類(lèi)的加載器去執(zhí)行据某。
- 如果父類(lèi)加載器還存在其父類(lèi)加載器,則進(jìn)一步向上委托诗箍,依次遞歸癣籽,請(qǐng)求最終到達(dá)頂層的啟動(dòng)類(lèi)加載器。
-
如果父類(lèi)加載器可以完成類(lèi)加載器任務(wù)就成功返回,倘若父類(lèi)加載器無(wú)法完成此加載任務(wù)筷狼,子加載器才會(huì)嘗試自己去加載瓶籽,這就是雙親委派模式。
雙親委派機(jī)制流程
思考:能不能自己寫(xiě)個(gè)java.lang.String類(lèi)埂材?
如下塑顺,我們自己定義一個(gè)java.lang.String類(lèi)。由于雙親委派機(jī)制楞遏,啟動(dòng)加載器會(huì)加載java核心類(lèi)庫(kù)的String類(lèi)(BootStrap啟動(dòng)類(lèi)加載器加載包名以java茬暇、javax、sun等開(kāi)頭的類(lèi))寡喝,而自己寫(xiě)的String類(lèi)根本沒(méi)有機(jī)會(huì)得到加載。
package java.lang;
public class String {
//
static{
System.out.println("我是自定義的String類(lèi)的靜態(tài)代碼塊");
}
public static void main(String[] args) {
System.out.println("hello,String");
}
}
5.2 沙箱安全機(jī)制
自定義String類(lèi)勒奇,在加載自定義String類(lèi)的時(shí)會(huì)率先使用引導(dǎo)類(lèi)加載器加載预鬓,而引導(dǎo)類(lèi)加載器在加載過(guò)程中會(huì)先加載jdk自帶的文件(rt.jar包中的java\lang\String.class),報(bào)錯(cuò)信息說(shuō)沒(méi)有main方法就是因?yàn)榧虞d的是rt.jar包中的String類(lèi)。這樣可以保證對(duì)java核心源代碼的保護(hù)赊颠,這就是沙箱安全機(jī)制
在jvm中表示兩個(gè)class對(duì)象是否為同一個(gè)類(lèi)存在的兩個(gè)必要條件:
- 類(lèi)的完整類(lèi)名必須一致格二,包括包名
- 加載這個(gè)類(lèi)的ClassLoader(指ClassLoader實(shí)例對(duì)象)必須相同
換句話(huà)說(shuō),在jvm中竣蹦,即使這兩個(gè)類(lèi)對(duì)象(class對(duì)象)來(lái)源同一個(gè)Class文件顶猜,被同一個(gè)虛擬機(jī)所加載,但只要加載它們的ClassLoader實(shí)例對(duì)象不同痘括,那么這兩個(gè)類(lèi)對(duì)象也是不相等的.
5.3 雙親委派機(jī)制的優(yōu)勢(shì)
- 避免類(lèi)的重復(fù)加載
-
保護(hù)程序安全长窄,防止核心API被隨意篡改
6 類(lèi)的主動(dòng)使用和被動(dòng)使用
①JVM必須知道一個(gè)類(lèi)型是有啟動(dòng)類(lèi)加載器加載的還是由用戶(hù)類(lèi)加載器加載的。如果一個(gè)類(lèi)型由用戶(hù)類(lèi)加載器加載的纲菌,那么jvm會(huì)將這個(gè)類(lèi)加載器的一個(gè)引用作為類(lèi)型信息的會(huì)議部分保存在方法區(qū)中
挠日。當(dāng)解析一個(gè)類(lèi)型到另一個(gè)類(lèi)型的引用的時(shí)候,JVM需要保證兩個(gè)類(lèi)型的加載器是相同的翰舌。
②java程序?qū)︻?lèi)的使用方式分為:主動(dòng)使用和被動(dòng)使用
主動(dòng)使用嚣潜,分為七種情況:
- 創(chuàng)建類(lèi)的實(shí)例
- 訪(fǎng)問(wèn)某各類(lèi)或接口的靜態(tài)變量,或者對(duì)靜態(tài)變量賦值
- 調(diào)用類(lèi)的靜態(tài)方法
- 反射椅贱,比如Class.forName(com.test.jvm.xxx)
- 初始化一個(gè)類(lèi)的子類(lèi)
- java虛擬機(jī)啟動(dòng)時(shí)被標(biāo)明為啟動(dòng)類(lèi)的類(lèi)
- JDK 7 開(kāi)始提供的動(dòng)態(tài)語(yǔ)言支持:
- java.lang.invoke.MethodHandle實(shí)例的解析結(jié)果REF_getStatic懂算、REF_putStatic、REF_invokeStatic句柄對(duì)應(yīng)的類(lèi)沒(méi)有初始化庇麦,則初始化计技。
除了以上七種情況,其他使用java類(lèi)的方式都被看作是對(duì)類(lèi)的被動(dòng)使用
女器,都不會(huì)導(dǎo)致類(lèi)的初始化酸役。