一、類加載子系統的作用
- 類加載子系統負責從文件系統或者網絡中加載Class文件,class文件在文件開頭有特定的文件標識;
- ClassLoader只負責class文件的加載,至於它是否可以運行,則由Execution Engine決定
- 加載的類信息存放於一塊成為方法區的內存空間。除了類信息之外,方法區還會存放運行時常量池信息,可能還包括字符串字面量和數字常量(這部分常量信息是Class文件中常量池部分的內存映射)
二、類加載子系統的概覽圖
類加載子系統主要包括三個環節:
- loading
- linking:verify,prepare,resolve
- initialization
三、類加載子系統各個環節解析:
1.Loading(加載)環節
- 通過一個類的全限定明獲取定義此類的二進制字節流;
- 將這個字節流所代表的的靜態存儲結構轉化為方法區的運行時數據;
- 在內存中生成一個代表這個類的java.lang.Class對象,作為方法區這個類的各種數據的訪問入口
2.Linking(鏈接)環節
- Verify(驗證)
(1)目的在於確保Class文件的字節流中包含信息符合當前虛擬機要求,保證被加載類的正確性,不會危害虛擬機自身安全。
(2)主要包括四種驗證,文件格式驗證,源數據驗證,字節碼驗證,符號引用驗證。
- Prepare(准備)環節
(1)為類變量分配內存並且設置該類變量的默認初始值,即零值;
(2)這里不包含用final修飾的static,因為final在編譯的時候就會分配了,准備階段會顯式初始化;
(3)不會為實例變量分配初始化,類變量會分配在方法去中,而實例變量是會隨着對象一起分配到java堆中。
- Resolve(解析)
(1)將常量池內的符號引用轉換為直接引用的過程。
(2)事實上,解析操作往往會伴隨着jvm在執行完初始化之后再執行。
(3)符號引用就是一組符號來描述所引用的目標。符號應用的字面量形式明確定義在《java虛擬機規范》的class文件格式中。直接引用就是直接指向目標的指針、相對偏移量或一個間接定位到目標的句柄。
(4)解析動作主要針對類或接口、字段、類方法、接口方法、方法類型等。對應常量池中的CONSTANT_Class_info/CONSTANT_Fieldref_info、CONSTANT_Methodref_info等。
3.Initialization(初始化)環節
- 初始化階段就是執行類構造器方法clinit()的過程。
- 此方法不需要定義,是javac編譯器自動收集類中的所有類變量的賦值動作和靜態代碼塊中的語句合並而來。
- 構造器方法中指令按語句在源文件中的出現的順序執行。
- Clinit不同於類的構造器(構造器是虛擬機視角下的init())
- 若該類有父類,jvm會保證子類的clinit()執行前,父類的clinit()已經執行完畢
- 虛擬機必須保證一個類的clinit()在多線程下被同步加載。
例子:
Number的值會先在prepare環節賦值為0,然后在initialzation環節先賦值為20,再賦值為10。
四、類加載器的分類
- VM支持兩種類型的加載器,分別為引導類加載器(BootStrap ClassLoader)和自定義類加載器(User-Defined ClassLoader)
- 從概念上來講,自定義類加載器一般指的是程序中由開發人員自定義的一類類加載器,但是java虛擬機規范卻沒有這么定義,而是將所有派生於抽象類ClassLoader的類加載器都划分為自定義類加載器。
自定義類與核心類庫的加載器:
- 對於用戶自定義類來說:使用系統類加載器AppClassLoader進行加載
- java核心類庫都是使用引導類加載器BootStrapClassLoader加載的
虛擬機自帶的加載器:
1) 啟動類加載器(引導類加載器,BootStrap ClassLoader)
- 這個類加載使用C/C++語言實現的,嵌套在JVM內部
- 它用來加載java的核心庫(JAVA_HOME/jre/lib/rt.jar/resources.jar或sun.boot.class.path路徑下的內容),用於提供JVM自身需要的類
- 並不繼承自java.lang.ClassLoader,沒有父加載器
- 加載拓展類和應用程序類加載器,並指定為他們的父加載器
- 處於安全考慮,BootStrap啟動類加載器只加載包名為java、javax、sun等開頭的類
2) 拓展類加載器(Extension ClassLoader)
- java語言編寫 ,由sun.misc.Launcher$ExtClassLoader實現。
- 派生於ClassLoader類
- 父類加載器為啟動類加載器
- 從java.ext.dirs系統屬性所指定的目錄中加載類庫,或從JDK的安裝目錄的jre/lib/ext子目錄(擴展目錄)下加載類庫。如果用戶創建的JAR放在此目錄下,也會由拓展類加載器自動加載
3) 應用程序類加載器(系統類加載器,AppClassLoader)
- java語言編寫, 由sun.misc.Launcher$AppClassLoader實現。
- 派生於ClassLoader類
- 父類加載器為拓展類加載器
- 它負責加載環境變量classpath或系統屬性 java.class.path指定路徑下的類庫
- 該類加載器是程序中默認的類加載器,一般來說,java應用的類都是由它來完成加載
- 通過ClassLoader.getSystemClassLoader()方法可以獲取到該類加載器
用戶自定義類加載器的作用:
- 隔離加載類
- 修改類加載的方式
- 拓展加載源
- 防止源碼泄漏
ClassLoader的常用方法及獲取方法
ClassLoader類,它是一個抽象類,其后所有的類加載器都繼承自ClassLoader(不包括啟動類加載器)
獲取ClassLoader的途徑:
五、雙親委派機制
Java虛擬機對class文件采用的是按需加載的方式,也就是說當需要使用該類時才會將她的class文件加載到內存生成的class對象。而且加載某個類的class文件時,java虛擬機采用的是雙親微拍模式,即把請求交由父類處理,它是一種任務委派 模式
雙親委派機制的優勢:
- 避免類的重復加載
- 保護程序安全,防止核心API被隨意篡改
六、沙箱安全機制
自定義String類,但是在加載子弟敬意String類的時候回率先使用引導類加載器加載,而引導類加載器在加載過程中會先加載jdk自帶的文件(rt.jar包中的java\lang\String.class),報錯信息說沒有main方法就是因為加載的是rt.jar包中的String類。這樣可以保證對java核心源代碼的保護,這就是沙箱安全機制。
注意:
在jvm中表示兩個class對象是否為同一個類存在的兩個必要條件:
- 類的完整類名必須一致,包括包名
- 加載這個類的ClassLoader(指ClassLoader實例對象)必須相同。換句話說,在jvm中,即使這兩個類對象(class對象)來源同一個Class文件,被同一個虛擬機所加載,但只要加載它們的ClassLoader實例對象不同,那么這兩個類對象也是不相等的.
-
JVM必須知道一個類型是有啟動類加載器加載的還是由用戶類加載器加載的。如果一個類型由用戶類加載器加載的,那么jvm會將這個類加載器的一個引用作為類型信息的會議部分保存在方法區中。當解析一個類型到另一個類型的引用的時候,JVM需要保證兩個類型的加載器是相同的。
七、類的主動使用和被動使用
主動使用,分為七種情況:
- 創建類的實例
- 訪問某各類或接口的靜態變量,或者對靜態變量賦值
- 調用類的靜態方法
- 反射 比如Class.forName(com.dsh.jvm.xxx)
- 初始化一個類的子類
- java虛擬機啟動時被標明為啟動類的類
- JDK 7 開始提供的動態語言支持:
- java.lang.invoke.MethodHandle實例的解析結果REF_getStatic、REF_putStatic、REF_invokeStatic句柄對應的類沒有初始化,則初始化
除了以上七種情況,其他使用java類的方式都被看作是對類的被動使用,都不會導致類的初始化。