java类是如何加载的?不知道classLoader和双亲委派,不是一个合格的程序员
目錄
詳細圖送上
類加載器子系統
類的加載過程
加載(loading)階段
鏈接(linking)
? ? 驗證(Verify)
? ? 準備(Prepare)
? ? 解析(Resolve)
初始化(Initialization)
類加載器
類加載器ClassLoader角色
各種類加載器
用java獲取類加載器
虛擬機自帶的類加載器
啟動類加載器(引導類加載器,Bootstrap ClassLoader)
擴展類加載器(Extension ClassLoader)
應用程序類加載器(系統類加載器,AppClassLoader)
獲取加載器加載的目錄
用戶自定義類加載器
用戶自定義類加載器的實現步驟
關于ClassLoader
獲取ClassLoader的途徑
雙親委派機制
自定義的String不會被識別:雙親委派機制
雙親委派的優勢
沙箱安全機制
其他
類的主動使用和被動使用
詳細圖送上
類加載器子系統
? ? 類加載器子系統負責從文件系統或者網絡中加載Class文件,class文件在文件開頭有特定的文件標識。
? ? ClassLoader只負責class文件的加載,至于它是否可以運行,則由Execution Engine(執行引擎)決定。
? ? 加載的類信息存放于一塊成為方法區的內存空間。除了類的信息外,方法區中還會存放運行時常量池信息,可能還包括字符串字面量和數字常量(這部分常量信息是Class文件中常量池部分的內存映射)。
類的加載過程
加載(loading)階段
? ? 1.通過一個類的全限定名獲取定義此類的二進制字節流。
? ? 2.將這個字節流所代表的靜態存儲結構轉化為方法區的運行時數據結構。
? ? 3.在內存中生成一個代表這個類的java.lang.Class對象,作為方法區這個類的各種數據的訪問入口。
補充:加載.class文件的方式:
? ? 1.從本地系統中直接加載。
? ? 2.通過網絡獲取,典型場景:Web Applet。
? ? 3.從zip壓縮包中讀取,成為日后jar、war格式的基礎。
? ? 4.運行時計算生成,使用最多的是:動態代理技術。
? ? 5.由其他文件生成,典型場景:JSP應用。
? ? 6.從專有數據庫中提取.class文件,比較少見。
? ? 7.從加密文件中獲取,典型的防Class文件被反編譯的保護措施。
鏈接(linking)
? ? 驗證(Verify)
?? ?? ? 1.目的在于確保class文件的字節流中包含信息符合當前虛擬機要求,保證被加載類的正確性,不會威海虛擬機自身安全。
?? ?? ? 2.主要包括四種驗證,文件格式驗證,元數據驗證,字節碼驗證,符號引用驗證。
?? ??? ?用UE打開之后,class文件都是以CAFEBABE開頭。
? ? 準備(Prepare)
?? ?? ? 1.為類變量分配內存并且設置該類變量的默認初始值,即零值。
?? ?? ? 2.這里不包含用final修飾的static,因為final在編譯的時候就會分配了,準備階段會顯式初始化;
?? ?? ? 3.這里不會為實例變量分配初始化,類變量會分配在方法區中,而實例變量是會隨著對象一起分配到Java堆中。
?? ?? ? 在準備階段,a只是會賦值為0,只有在初始化階段才會賦值為1。
? ? 解析(Resolve)
?? ?? ? 1.將常量池內的符號引用轉換為直接引用的過程。
?? ?? ? 2.事實上,解析操作往往會伴隨著JVM在執行完初始化之后再執行。
?? ?? ? 3.符號引用就是一組符號來描述所引用的目標。符號引用的字面量形式明確定義在《java虛擬機規范》的Class文件格式中。直接引用就是直接指向目標的指針、相對偏移量或一個間接定位到目標的句柄。
?? ?? ? 4.解析動作主要針對類或接口、字段、類方法、接口方法、方法類型等。對應常量池中的CONSTANT_Class_info、CONSTANT_Fieldref_info、CONSTANT_Methodref_info等。
初始化(Initialization)
? ? 1.初始化階段就是執行類構造器方法<clinit>()的過程。
? ? 2.此方法不需定義,是javac編譯器自動收集類中的所有類變量的賦值動作和靜態代碼塊中的語句合并而來(如果沒有static變量的賦值動作和靜態代碼塊,則沒有<clinit>()方法)。
? ? 3.構造器方法中指令按語句在源文件中出現的順序執行。
? ? 4.<clinit>()不同于類的構造器。(關聯:構造器是虛擬機視角下的<init>())
? ? 5.若該類具有父類,JVM會保證子類的<clinit>()執行前,父類的<clinit>()已經執行完畢。
? ? 6.虛擬機必須保證一個類的<clinit>()方法在多線程下被同步加鎖(相當于static代碼塊默認帶鎖)。
?? ?順序執行案例:
類加載器
類加載器ClassLoader角色
1.class file存在于本地硬盤上,可以理解為設計師畫在紙上的模板,而最終這個模板在執行的時候是要加載到JVM當中來根據這個文件實例化出n個一模一樣的實例。
2.class file加載到JVM中,被稱為DNA元數據模板,放在方法區。
3.在.class文件->JVM->最終成為元數據模板,此過程就要一個運輸工具(類裝載器Class Loader),扮演一個快遞員的角色。
各種類加載器
? ? JVM支持兩種類型的類加載器,分別為引導類加載器(Bootstrap ClassLoader)和自定義類加載器(User-Defined ClassLoader)。
? ? 從概念上來將,自定義類加載器一般指的是程序中由開發人員自定義的一類類加載器,但是Java虛擬機規范卻沒有這么定義,而是將所有派生于抽象類ClassLoader的類加載器都劃分為自定義類加載器。
? ? 無論類加載器的類型如何劃分,在程序中我們最常見的類加載器始終只有3個,如下所示:
用java獲取類加載器
虛擬機自帶的類加載器
啟動類加載器(引導類加載器,Bootstrap ClassLoader)
? ? 1.這個類加載使用C/C++語言實現的,嵌套在JVM內部。
? ? 2.它用來加載Java的核心庫(JAVA_HOME/jre/lib/rt.jar、resources.jar或sun.boot.class.path路徑下的內容),用于提供JVM自身需要的類。
? ? 3.并不繼承自java.lang.ClassLoader,沒有父加載器。
? ? 4.加載擴展類和應用程序類加載器,并指定為他們的父類加載器。
? ? 5.出于安全考慮,Bootstrap啟動類加載器只加載包名為java、javax、sun等開頭的類。
擴展類加載器(Extension ClassLoader)
? ? 1.Java語言編寫,由sun.misc.Launcher$ExtClassLoader實現。
? ? 2.派生于ClassLoader類。
? ? 3.父類加載器為啟動類加載器。
? ? 4.從java.ext.dirs系統屬性所指定的目錄中加載類庫,或從JDK的安裝目錄的jre/lib/ext子目錄(擴展目錄)下加載類庫。如果用戶創建的JRE放在此目錄下,也會自動由擴展類加載器加載。
應用程序類加載器(系統類加載器,AppClassLoader)
? ? 1.java語言編寫,由sun.misc.Launcher$AppClassLoader實現。
? ? 2.派生于ClassLoader類。
? ? 3.父類加載器為擴展類加載器。
? ? 4.它負責加載環境變量classpath或系統屬性java.class.path指定路徑下的類庫。
? ? 5.該類加載器時程序中默認的類加載器,一般來說,Java應用的類都是由它來完成加載。
? ? 6.通過ClassLoader#getSystemClassLoader()方法可以獲取到該類加載器。
獲取加載器加載的目錄
用戶自定義類加載器
? ? 1.在Java的日常應用程序開發中,類的加載幾乎是由上述3種類加載器相互配合執行的,在必要時,我們還可以自定義類加載器,來定制類的加載方式。
為什么要自定義類加載器?:1.隔離加載類;2.修改類加載的方式;3.擴展加載源;4.防止源碼泄漏。
用戶自定義類加載器的實現步驟
? ? 1.開發人員可以通過繼承抽象類java.lang.ClassLoader類的方式,實現自己的類加載器,以滿足一些特殊的需求。
? ? 2.在JDK1.2之前,在自定義類加載器時,總會去繼承ClassLoader類并重寫loadClass()方法,從而實現自定義的類加載器,但是在JDK1.2之后已不再建議用戶去覆蓋loadClass()方法,而是建議把自定義的類加載邏輯卸載findCLass()方法中。
? ? 3.在編寫自定義類加載器時,如果沒有太過于復雜的需求,可以直接繼承URLClassLoader類,這樣就可以避免自己去編寫findClass()方法及其獲取字節碼流的方式,使自定義類加載器編寫更加簡潔。
關于ClassLoader
獲取ClassLoader的途徑
雙親委派機制
? ? Java虛擬機對class文件采用的是按需加載的方式,也就是說當需要使用該類時才會將它的class文件加載到內存生成class對象。而且加載某個類的class文件時,Java虛擬機采用的是雙親委派模式,即把請求交由父類處理,它的一種任務委派模式。
工作原理:
? ? 1.如果一個類加載器收到了類加載請求,它并不會自己先去加載,而是把這個請求委托給父類的加載器去執行;
? ? 2.如果父類加載器還存在其父類加載器,則進一步向上委托,依次遞歸,請求最終將到達頂層的啟動類加載器;
? ? 3.如果父類加載器可以完成類加載任務,就成功返回,倘若父類加載器無法完成此加載任務,子加載器才會嘗試自己去加載,這就是雙親委派模式。
自定義的String不會被識別:雙親委派機制
雙親委派的優勢
? ? 1.避免類的重復加載。
? ? 2.保護程序安全,防止核心API被隨意篡改。
? ? 自定義類:java.lang.String
自定義類java.lang.xxxx,執行的時候會報這個錯誤:
沙箱安全機制
? ? 自定義String類,但是在加載自定義String類的時候會率先使用引導類加載器加載,而引導類加載器在加載的過程中會先加載jdk自帶的文件(rt.jar包中java.lang.String),報錯信息說沒有main方法就是因為加載的是rt.jar包中的String類。這樣可以保證對java核心源代碼的保護,這就是沙箱安全機制。
其他
在JVM中表示兩個class對象是否為同一個類存在兩個必要條件:
? ? 1.類的完整類名必須一致,包括包名。
? ? 2.加載這個類的ClassLoader(指ClassLoader實例對象)必須相同。
換句話說,在JVM中,即使這兩個類對象(class對象)來源同一個Class文件,被同一個虛擬機所加載,但只要加載它們的ClassLoader實例對象不同,那么這兩個類對象也是不相等的。
?? ?JVM必須知道一個類型是由啟動加載器加載的還是由用戶類加載器加載的。如果一個類型是由用戶類加載器加載的,那么JVM會將這個類加載器的一個引用作為類型信息的一部分保存在方法區中。當解析一個類型到另一個類型的引用的時候,JVM需要保證這兩個類型的類加載器是相同的。
類的主動使用和被動使用
Java程序對類的使用方式分為:主動使用和被動使用。
主動使用,又分為七種情況:
? ? 1.創建類的實例。
? ? 2.訪問某個類或接口的靜態變量,或者對該靜態變量賦值。
? ? 3.調用類的靜態方法。
? ? 4.反射(比如Class.forName)
? ? 5.初始化一個類的子類。
? ? 6.Java虛擬機啟動時被標明為啟動類的類。
? ? 7.JDK7開始提供的動態語言支持:java.lang.invoke.MethodHandle實例的解析結果;REF_getStatic、REF_putStatic、REF_invokeStatic句柄對應的類沒有初始化,則初始化。
除了以上七種情況,其他使用Java類的方式都看作是類的被動使用,都不會導致類的初始化。
被動使用:
? ? 1.通過子類引用父類的靜態字段,為子類的被動使用,不會導致子類初始化。
? ? 2.通過數組定義類引用類,為類的被動使用,不會觸發此類的初始化E[] e = new E[10];
? ? 3.常量在編譯階段會存入調用方法所在的類的常量池中(這個例子存在F類的常量池中),本質上沒有直接引用到定義常量的類,因此不會觸發定義常量的類的初始化。
總結
以上是生活随笔為你收集整理的java类是如何加载的?不知道classLoader和双亲委派,不是一个合格的程序员的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: java代码编译之后是如何运行的?不知道
- 下一篇: jvm运行时数据区是干啥的?CPU切换线