pyqt5从子目录加载qrc文件_【JVM系统学习之路】一篇看懂类加载
JVM系統(tǒng)學(xué)習(xí)之路系列演示代碼地址:https://github.com/mtcarpenter/JavaTutorial
嗨嘍,小伙伴大家好,我是小春哥,今天是打卡 【JVM系統(tǒng)學(xué)習(xí)之路】?的第二篇文章 類加載子系統(tǒng)?,在學(xué)習(xí)本章節(jié)首先回顧 上一章節(jié)【JVM系統(tǒng)學(xué)習(xí)之路】JVM與Java體系結(jié)構(gòu)的JVM整體結(jié)構(gòu)。此圖一定牢記于心,在接下來的 JVM 知識點都是從此圖展開進行的,如果了解看過JVM 面試題,大家對此圖應(yīng)該還是比較熟悉,小春哥此圖我都了解了,想要一個更完整的 JVM 架構(gòu)圖,有嗎?有的,這事兒必須滿足。請接住下圖:
JVM 是一塊難啃的骨頭,吃骨髓之前,我們清洗,入鍋,小火慢燉等工序,我們就從類加載開始啃,
類加載器與類的加載過程
- 類加載器子系統(tǒng)負責(zé)從文件系統(tǒng)或者網(wǎng)絡(luò)中加載 Class?文件, class?文件在文件開頭有特定的文件標識。
- ClassLoader?只負責(zé) class?文件的加載,至于它是否可以運行,則由 Execution Engine?決定。
- 加載的類信息存放于一塊稱為方法區(qū)的內(nèi)存空間。除了類的信息外,方法區(qū)中還會存放運行時常量池信息,可能還包括字符串字面量和數(shù)字常量(這部分常量信息是 Class?文件中常量池部分的內(nèi)存映射)
- class file?存在于本地硬盤上,可以理解為設(shè)計師畫在紙上的模板,而最終這個模板在執(zhí)行的時候是要加載到 JVM 當中來根據(jù)這個文件實例化出 n 個一模一樣的實例。
- class file?加載到 JVM 中,被稱為 DNA 元數(shù)據(jù)模板,放在方法區(qū)。
- 在 .class文件->JVM->最終成為元數(shù)據(jù)模板?,此過程就要一個運輸工具(類裝載器 Class Loader?),扮演一個快遞員的角色
類的加載過程
/**?*?類加載子系統(tǒng)
?*/
public?class?HelloLoader?{
????public?static?void?main(String[]?args)?{
????????System.out.println("我已經(jīng)被加載啦");
????}
}
此段代碼的加載過程如下
更為完整的加載過程,有加載、驗證、準備、解析、初始化,如下圖:
加載階段
- 通過一個類的全限定名獲取定義此類的二進制字節(jié)流
- 將這個字節(jié)流所代表的靜態(tài)存儲結(jié)構(gòu)轉(zhuǎn)化為方法區(qū)的運行時數(shù)據(jù)結(jié)構(gòu)
- 在內(nèi)存中生成一個代表這個類的 java.lang.Class?對象,作為方法區(qū)這個類的各種數(shù)據(jù)的訪問入口
加載class文件的方式
- 從本地系統(tǒng)中直接加載
- 通過網(wǎng)絡(luò)獲取,典型場景:Web Applet
- 從zip壓縮包中讀取,成為日后 jar、war 格式的基礎(chǔ)
- 運行時計算生成,使用最多的是:動態(tài)代理技術(shù)
- 由其他文件生成,典型場景:JSP 應(yīng)用從專有數(shù)據(jù)庫中提取 .class 文件,比較少見
- 從加密文件中獲取,典型的防 Class 文件被反編譯的保護措施
鏈接階段
驗證 Verify
- 目的在于確保 Class?文件的字節(jié)流中包含信息符合當前虛擬機要求,保證被加載類的正確性,不會危害虛擬機自身安全。
- 主要包括四種驗證, 文件格式驗證?, 元數(shù)據(jù)驗證?, 字節(jié)碼驗證?, 符號引用驗證?。
工具:Binary Viewer查看是否 class 文件,后續(xù)會講解此工具
準備 Prepare
- 為類變量分配內(nèi)存并且設(shè)置該類變量的默認初始值,即零值。
????private?static?int?a?=?1;??//?準備階段為0,在下個階段,也就是初始化的時候才是1
????public?static?void?main(String[]?args)?{
????????System.out.println(a);
????}
}
上面的變量a在準備階段會賦初始值,但不是 1 ,而是 0。
- 這里不包含用 final 修飾的 static ,因為final在編譯的時候就會分配了,準備階段會顯式初始化;
- 這里不會為實例變量分配初始化,類變量會分配在方法區(qū)中,而實例變量是會隨著對象一起分配到Java堆中。
解析 Resolve
- 將常量池內(nèi)的符號引用轉(zhuǎn)換為直接引用的過程。
- 事實上,解析操作往往會伴隨著JVM在執(zhí)行完初始化之后再執(zhí)行。
- 符號引用就是一組符號來描述所引用的目標。符號引用的字面量形式明確定義在《java虛擬機規(guī)范》的class文件格式中。直接引用就是直接指向目標的指針、相對偏移量或一個間接定位到目標的句柄。
- 解析動作主要針對類或接口、字段、類方法、接口方法、方法類型等。對應(yīng)常量池中的 CONSTANT Class info?、 CONSTANT Fieldref info?、 CONSTANT Methodref info?等。
初始化階段
- 初始化階段就是執(zhí)行類構(gòu)造器法()的過程。
- 此方法不需定義,是 javac 編譯器?自動收集類中的所有類變量的賦值動作和靜態(tài)代碼塊中的語句合并而來。
- 也就是說,當我們代碼中包含 static 變量的時候,就會有 clinit 方法
- 構(gòu)造器方法中指令按語句在源文件中出現(xiàn)的順序執(zhí)行。
- 不同于類的構(gòu)造器。(關(guān)聯(lián):構(gòu)造器是虛擬機視角下的())若該類具有父類,JVM會保證子類的()執(zhí)行前,父類的()已經(jīng)執(zhí)行完畢。
- 任何一個類在聲明后,都有生成一個構(gòu)造器,默認是空參構(gòu)造器
????private?static?int?num?=?1;
????static?{
????????num?=?2;
????????number?=?20;
????????System.out.println(num);
????????System.out.println(number);??//報錯,非法的前向引用
????}
????private?static?int?number?=?10;
????public?static?void?main(String[]?args)?{
????????System.out.println(ClassInitTest.num);?//?2
????????System.out.println(ClassInitTest.number);?//?10
????}
}
類加載器分類
- JVM支持兩種類型的類加載器 。分別為 引導(dǎo)類加載器(Bootstrap ClassLoader)? 和 自定義類加載器(User-Defined ClassLoader)?。
- 從概念上來講,自定義類加載器一般指的是程序中由開發(fā)人員自定義的一類類加載器,但是 Java 虛擬機規(guī)范卻沒有這么定義,而是將所有派生于抽象類 ClassLoader?的類加載器都劃分為自定義類加載器。
- 無論類加載器的類型如何劃分,在程序中我們最常見的類加載器有 引導(dǎo)類加載器,Bootstrap ClassLoader?、 擴展類加載器(Extension ClassLoader)?、 系統(tǒng)類加載器(AppClassLoader)?,如下所示:
這里的四者之間是包含關(guān)系,不是上層和下層,也不是子系統(tǒng)的繼承關(guān)系。下面通過一個例子,獲取它不同的加載器
public?class?ClassLoaderTest?{????public?static?void?main(String[]?args)?{
????????//?獲取系統(tǒng)類加載器
????????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);
????????
????????//?獲取String類型的加載器
????????ClassLoader?classLoader1?=?String.class.getClassLoader();
????????System.out.println(classLoader1);
????}
}
上面得到的結(jié)果,可以看出根加載器無法直接通過代碼獲取,同時目前用戶代碼所使用的加載器為系統(tǒng)類加載器。同時我們通過獲取 String 類型的加載器,發(fā)現(xiàn)是 null,那么說明 String 類型是通過根加載器進行加載的,也就是說 Java 的核心類庫都是使用根加載器進行加載的。
sun.misc.Launcher$AppClassLoader@18b4aac2sun.misc.Launcher$ExtClassLoader@1540e19d
null
sun.misc.Launcher$AppClassLoader@18b4aac2
null
虛擬機自帶的加載器
啟動類加載器(引導(dǎo)類加載器,Bootstrap ClassLoader)
- 這個類加載使用 C/C++語言?實現(xiàn)的,嵌套在 JVM 內(nèi)部。
- 它用來加載 Java 的核心庫( JAVAHOME/jre/1ib/rt.jar?、 resources.jar?或 sun.boot.class.path?路徑下的內(nèi)容),用于提供 JVM 自身需要的類
- 并不繼承自 Java.lang.ClassLoader?,沒有父加載器。
- 加載擴展類和應(yīng)用程序類加載器,并指定為他們的父類加載器。
- 出于安全考慮, Bootstrap 啟動類加載器?只加載包名為 java?、 javax?、 sun?等開頭的類
擴展類加載器(Extension ClassLoader)
- Java語言編寫,由 sun.misc.Launcher$ExtClassLoader?實現(xiàn)。
- 派生于 ClassLoader?類
- 父類加載器為啟動類加載器
- 從 java.ext.dirs?系統(tǒng)屬性所指定的目錄中加載類庫,或從JDK的安裝目錄的jre/1ib/ext子目錄(擴展目錄)下加載類庫。如果用戶創(chuàng)建的JAR放在此目錄下,也會自動由擴展類加載器加載。
應(yīng)用程序類加載器(系統(tǒng)類加載器,AppClassLoader)
- java語言編寫,由 sun.misc.LaunchersAppClassLoader?實現(xiàn)
- 派生于 ClassLoader?類
- 父類加載器為擴展類加載器
- 它負責(zé)加載環(huán)境變量 classpath?或系統(tǒng)屬性 java.class.path?指定路徑下的類庫
- 該類加載是程序中默認的類加載器,一般來說,Java 應(yīng)用的類都是由它來完成加載
- 通過 classLoader#getSystemclassLoader()?方法可以獲取到該類加載器
用戶自定義類加載器
- 在Java的日常應(yīng)用程序開發(fā)中,類的加載幾乎是由上述3種類加載器相互配合執(zhí)行的,在必要時,我們還可以自定義類加載器,來定制類的加載方式。
- 為什么要自定義類加載器?
- 隔離加載類
- 修改類加載的方式
- 擴展加載源
- 防止源碼泄漏
用戶自定義類加載器實現(xiàn)步驟:
- 開發(fā)人員可以通過繼承抽象類 Java.lang.ClassLoader?類的方式,實現(xiàn)自己的類加載器,以滿足一些特殊的需求
- 在 JDK1.2?之前,在自定義類加載器時,總會去繼承 ClassLoader?類并重寫 loadClass()? 方法,從而實現(xiàn)自定義的類加載類,但是在 JDK1.2 之后已不再建議用戶去覆蓋 loadclass()?方法,而是建議把自定義的類加載邏輯寫在 findclass()?方法中
- 在編寫自定義類加載器時,如果沒有太過于復(fù)雜的需求,可以直接繼承 URIClassLoader?類,這樣就可以避免自己去編寫 findclass()?方法及其獲取字節(jié)碼流的方式,使自定義類加載器編寫更加簡潔。
查看根加載器所能加載的目錄
public?class?ClassLoaderTest1?{????public?static?void?main(String[]?args)?{
????????System.out.println("*********啟動類加載器************");
????????//?獲取BootstrapClassLoader?能夠加載的API的路徑
????????URL[]?urls?=?sun.misc.Launcher.getBootstrapClassPath().getURLs();
????????for?(URL?url?:?urls)?{
????????????System.out.println(url.toExternalForm());
????????}
????????//?從上面路徑中,隨意選擇一個類,來看看他的類加載器是什么:得到的是null,說明是??根加載器
????????ClassLoader?classLoader?=?Provider.class.getClassLoader();
????}
}
得到的結(jié)果如下:
*********啟動類加載器************file:/E:/Software/JDK1.8/Java/jre/lib/resources.jar
file:/E:/Software/JDK1.8/Java/jre/lib/rt.jar
file:/E:/Software/JDK1.8/Java/jre/lib/sunrsasign.jar
file:/E:/Software/JDK1.8/Java/jre/lib/jsse.jar
file:/E:/Software/JDK1.8/Java/jre/lib/jce.jar
file:/E:/Software/JDK1.8/Java/jre/lib/charsets.jar
file:/E:/Software/JDK1.8/Java/jre/lib/jfr.jar
file:/E:/Software/JDK1.8/Java/jre/classes
null
ClassLoader 的使用說明
ClassLoader?類,它是一個抽象類,其后所有的類加載器都繼承自 ClassLoader(不包括啟動類加載器)
| getParent() | 返回該類加載器的父類加載器。 |
| loadClass(String name) | 加載名稱為 name 的類,返回的結(jié)果是 java.lang.Class 類的實例。 |
| findClass(String name) | 查找名稱為 name 的類,返回的結(jié)果是 java.lang.Class 類的實例。 |
| findLoadedClass(String name) | 查找名稱為 name 的已經(jīng)被加載過的類,返回的結(jié)果是 java.lang.Class 類的實例。 |
| defineClass(String name, byte[] b, int off, int len) | 把字節(jié)數(shù)組 b 中的內(nèi)容轉(zhuǎn)換成 Java 類,返回的結(jié)果是 java.lang.Class 類的實例。這個方法被聲明為 final 的。 |
| resolveClass(Class> c) | 鏈接指定的 Java 類。 |
sun.misc.Launcher?它是一個 java 虛擬機的入口應(yīng)用
獲取 ClassLoader?的途徑
- 獲取當前 ClassLoader:clazz.getClassLoader()
- 獲取當前線程上下文的 ClassLoader:Thread.currentThread().getContextClassLoader()
- 獲取系統(tǒng)的 ClassLoader:ClassLoader.getSystemClassLoader()
- 獲取調(diào)用者的 ClassLoader:DriverManager.getCallerClassLoader()
雙親委派機制
Java 虛擬機對 class 文件采用的是 按需加載? ?的方式,也就是說當需要使用該類時才會將它的 class 文件加載到內(nèi)存生成 class 對象。而且加載某個類的 class 文件時,Java 虛擬機采用的是 雙親委派模式?,即把請求交由父類處理,它是一種 任務(wù)委派模式?。工作原理
- 如果一個類加載器收到了類加載請求,它并不會自己先去加載,而是把這個請求委托給父類的加載器去執(zhí)行;
- 如果父類加載器還存在其父類加載器,則進一步向上委托,依次遞歸,請求最終將到達頂層的啟動類加載器;
- 如果父類加載器可以完成類加載任務(wù),就成功返回,倘若父類加載器無法完成此加載任務(wù),子加載器才會嘗試自己去加載,這就是 雙親委派模式?。
雙親委派機制舉例
當我們加載 jdbc.jar?用于實現(xiàn)數(shù)據(jù)庫連接的時候,首先我們需要知道的是 jdbc.jar?是基于 SPI 接口進行實現(xiàn)的,所以在加載的時候,會進行雙親委派,最終從根加載器中加載 SPI核心類,然后在加載 SPI 接口類,接著在進行反向委派,通過線程上下文類加載器進行實現(xiàn)類 jdbc.jar?的加載。
沙箱安全機制
自定義 string 類,但是在加載自定義 String 類的時候會率先使用引導(dǎo)類加載器加載,而引導(dǎo)類加載器在加載的過程中會先加載 jdk 自帶的文件(rt.jar包中 java\lang\String.class?),報錯信息說沒有 main 方法,就是因為加載的是 rt.jar 包中的 string 類。這樣可以保證對 java 核心源代碼的保護,這就是沙箱安全機制。
雙親委派機制的優(yōu)勢
通過上面的例子,我們可以知道,雙親機制可以:
- 避免類的重復(fù)加載
- 保護程序安全,防止核心API被隨意篡改
- 自定義類:java.lang.String
- 自定義類:java.lang.ShkStart?(報錯:阻止創(chuàng)建 java.lang?開頭的類)
如何判斷兩個class對象是否相同
- 在JVM中表示兩個class 對象是否為同一個類存在兩個必要條件:
- 類的完整類名必須一致,包括包名。
- 加載這個類的? ?ClassLoader?(指 ClassLoader?實例對象)必須相同。
- 換句話說,在 JVM 中,即使這兩個類對象(class對象)來源同一個 Class 文件,被同一個虛擬機所加載,但只要加載它們的 ClassLoader?實例對象不同,那么這兩個類對象也是不相等的。
- JVM必須知道一個類型是由啟動加載器加載的還是由用戶類加載器加載的。如果一個類型是由用戶類加載器加載的,那么JVM會將這個類加載器的一個引用作為類型信息的一部分保存在方法區(qū)中。當解析一個類型到另一個類型的引用的時候,JVM需要保證這兩個類型的類加載器是相同的。
類的主動使用和被動使用
Java程序?qū)︻惖氖褂梅绞椒譃?#xff1a;主動使用和被動使用。
- 主動使用,又分為七種情況:
- 創(chuàng)建類的實例
- 訪問某個類或接口的靜態(tài)變量,或者對該靜態(tài)變量賦值
- 調(diào)用類的靜態(tài)方法I
- 反射(比如:Class.forName("com.atguigu.Test"?))
- 初始化一個類的子類
- Java虛擬機啟動時被標明為啟動類的類
- JDK7開始提供的動態(tài)語言支持:
- java.lang.invoke.MethodHandle?實例的解析結(jié)果 REF getStatic、REF putStatic、REF invokeStatic?句柄對應(yīng)的類沒有初始化,則初始化
- 除了以上七種情況,其他使用Java類的方式都被看作是對類的被動使用,都不會導(dǎo)致類的初始化。
總結(jié)
本篇文章開始回顧了 JVM 結(jié)構(gòu)圖,并完善了 JVM 完整的結(jié)構(gòu)圖,接下來開始學(xué)習(xí)類加載,類加載的過程有分為三個階段,分別是 加載階段(引導(dǎo)類、擴展類、系統(tǒng)類加載器)?、 鏈接階段(驗證、準備、解析)?、 初始化階段?。接下來還介紹了虛擬機的加載器, 啟動類加載器?、 擴展類加載器?、 引用類加載器?。還有被面試問的比較多的是雙親委派機制。
歡迎關(guān)注公眾號 山間木匠 , 我是小春哥,從事 Java 后端開發(fā),會一點前端、通過持續(xù)輸出系列技術(shù)文章以文會友,如果本文能為您提供幫助,歡迎大家關(guān)注、在看、 點贊、分享支持,我們下期再見!
總結(jié)
以上是生活随笔為你收集整理的pyqt5从子目录加载qrc文件_【JVM系统学习之路】一篇看懂类加载的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python读取只读word只读_人生苦
- 下一篇: c++ 链表_算法学习笔记 - 链表 -