將類的
.class
文件讀入內存,併爲之建立一個java.lang.Class
的對象,而.class
文件只有被確的加載到JVM
正中才能運行和使用。java
類從被加載到虛擬機內存中開始,到卸載出內存爲止,它的生命週期包括了:加載(Loading
)、驗證(Verification
)、準備(Preparation
)、解析(Resolution
)、初始化(Initialization
)、使用(Using
)、卸載(Unloading
)七個階段,其中驗證、準備、解析三個部分統稱連接。 程序員
加載階段是「類加載機制」中的一個階段,這個階段一般也被稱做「裝載」,主要完成:數組
java
堆中生成一個表明這個類的java.lang.Class
對象,做爲方法區這些數據的訪問入口驗證是鏈接階段的第一步,這一階段的目的是爲了確保Class文件的字節流中包含的信息符合虛擬機的要求,而且不會危害虛擬機自身的安全。驗證階段大體會完成如下4個階段的校驗動做:文件格式驗證、元數據驗證、字節碼驗證和符號引用驗證。安全
這一階段目的是驗證二進制字節流是否符合Class文件格式的規範,而且能被當前版本的虛擬機處理,檢測內容包括如下幾點:數據結構
0xCAFEBABY
)開頭。CONSTANT_Utf8_info
型常量是否有不符合UTF8數據編碼的數據。Class
文件中各個部分及文件自己是否有被刪除的或附加的信息。這一階段主要對字節碼的描述信息進行語義分析,以保證其描述信息符合java語言規範,這階段的驗證點可能包括如下幾點:jvm
final
修飾)final
字段值等)這一階段目的主要目的是肯定程序語義是合法的、符合邏輯的。這個階段主要對類的字節碼進行校驗分析,保證該類的方法不會在運行時作出危害虛擬機安全的事:ide
int
類型的數據使用時按long
類型加載進本地變量表中。這一階段用來將符號引用轉換爲直接引用的時候,這個轉化將在解析階段中發生,符號引用驗證能夠看作是類對自身之外(常量池中各類符號引用)的信息進行匹配性校驗,一般須要校驗如下內容:post
準備階段是正式爲類變量分配內存並設置初始值的階段,這些變量所使用的內存都將在方法區分配。實例變量會在對象實例化的時候跟對象一塊兒在java堆中分配。這裏的初始值指的是一般狀況下的零值。假設一個類變量定義爲:學習
public static int a=123;
複製代碼
那麼變量
a
初始化的值是0
而不是123
。若是變量同時是final
類型,那麼準備階段就會被賦值爲123
,沒必要等到初始化階段再賦值。this
解析階段是將虛擬機常量池內的符號引用替換爲直接引用的過程。解析動做主要針對類或接口、字段、類方法、接口方法、方法類型、方法句柄和調用點限定符7類符號進行。可能你們有疑問Class
文件中哪有這麼多內容,其實上面也說了,是針對常量池。不論是CLass
文件中的方法表仍是字段表,不能直接表示的內容,基本都會直接或間接存在常量池中,所以解析過程就是針對常量池中的數據類型進行解析的。
要把一個從未解析過的符號引用N解析爲一個類或接口的直接引用,虛擬機須要完成如下3個步驟:
[Ljava/lang/Integer
」的形式按照第一點的規則加載數組元素類型。若是N的描述符如前面所假設的形式,須要加載的類型就是java.lang.Integer
,接着由虛擬機生成一個表明次數組維度和元素的數組對象。對字段表內class_index
項中索引的CONSTANT_Class_info
符號引用進行解析,也就是字段所屬的類或接口的符號引用,若是解析這個類或符號引用的過程當中出現任何異常,都會致使字段符號引用解析的失敗。若是解析成功,這個字段對應的類或接口用C表示,接下來沿着A和它的父類/父接口尋找是否有這個字段,若是有會進行權限驗證,若是不具有權限則拋出異常。若是這個過程不出錯,則會在找到符合字段的時候返回這個字段的直接飲用,查找結束。
class_index
項中索引的方法所屬的類或接口的符號引用,解析成功用C表示。接口方法須要先解析出接口方法表的class_index
項中索引的方法所屬的類或接口的符號引用。
class_index
中的索引C
是個類而不是接口,直接拋出異常。類的初始化階段是類加載過程的最後一步,在準備階段,類變量已賦過一次系統要求的初始值,而在初始化階段,則是根據程序員經過程序制定的主觀計劃去初始化類變量和其餘資源,或者能夠從另一個角度來表達:初始化階段是執行類構造器()方法的過程。
在如下四種狀況下初始化過程會被觸發執行:
new、getstatic、putstatic或invokestatic
這4條字節碼指令時,若是類沒有進行過初始化,則需先觸發其初始化。生成這4條指令的最多見的java
代碼場景是:使用new關鍵字實例化對象、讀取或設置一個類的靜態字段(被final
修飾、已在編譯器把結果放入常量池的靜態字段除外)的時候,以及調用類的靜態方法的時候。java.lang.reflect
包的方法對類進行反射調用的時候jvm
啓動時,用戶指定一個執行的主類(包含main
方法的那個類),虛擬機會先初始化這個類初始化類的方法
new
時候會初始化類Class.forName("com.geminno");
(java Test)
,直接用java.exe
運行某個類;類中成員初始化的順序:
父類的靜態字段——>父類靜態代碼塊——>子類靜態字段——>子類靜態代碼塊——>父類成員變量(非靜態字段)——>父類非靜態代碼塊——>父類構造器——>子類成員變量——>子類非靜態代碼塊——>子類構造器
複製代碼
對於任何一個類,都須要由加載它的類加載器和這個類來確立其在JVM中的惟一性。也就是說,兩個類來源於同一個Class
文件,而且被同一個類加載器加載,這兩個類才相等。
從虛擬機的角度來講,只存在兩種不一樣的類加載器:一種是啓動類加載器(Bootstrap ClassLoader)
,該類加載器使用C++
語言實現,屬於虛擬機自身的一部分。另一種就是全部其它的類加載器
,這些類加載器是由Java
語言實現,獨立於JVM
外部,而且所有繼承自抽象類java.lang.ClassLoader
。
大部分Java程序通常會使用到如下三種系統提供的類加載器:
①
啓動類加載器(Bootstrap ClassLoader)
:負責加載JAVA_HOME\lib
目錄中而且能被虛擬機識別的類庫到JVM
內存中,若是名稱不符合的類庫即便放在lib
目錄中也不會被加載。該類加載器沒法被Java程序直接引用。
②擴展類加載器(Extension ClassLoader)
:該加載器主要是負責加載JAVA_HOME\lib\
,該加載器能夠被開發者直接使用。
③應用程序類加載器(Application ClassLoader)
:該類加載器也稱爲系統類加載器,它負責加載用戶類路徑(Classpath)
上所指定的類庫,開發者能夠直接使用該類加載器,若是應用程序中沒有自定義過本身的類加載器,通常狀況下這個就是程序中默認的類加載器。
主要體如今ClassLoader
的loadClass()
方法中,思路很簡單:先檢查是否已經被加載過,若沒有加載則調用父類加載器的loadClass()
方法,若父類加載器爲空則默認使用啓動類加載器做爲父類加載器。若是父類加載器加載失敗,拋出ClassNotFoundException
異常後,調用本身的findClass()
方法進行加載。
若要實現自定義類加載器,只須要繼承java.lang.ClassLoader
類,而且重寫其findClass()
方法便可java.lang.ClassLoader
類的基本職責就是根據一個指定的類的名稱,找到或者生成其對應的字節代碼,而後從這些字節代碼中定義出一個Java
類,即java.lang.Class
類的一個實例。除此以外,ClassLoader
還負責加載Java
應用所需的資源,如圖像文件和配置文件等,ClassLoader
中與加載類相關的方法以下:
方法說明
getParent() 返回該類加載器的父類加載器。
loadClass(String name) 加載名稱爲 二進制名稱爲name 的類,返回的結果是 java.lang.Class 類的實例。
findClass(String name) 查找名稱爲 name 的類,返回的結果是 java.lang.Class 類的實例。
findLoadedClass(String name) 查找名稱爲 name 的已經被加載過的類,返回的結果是 java.lang.Class 類的實例。
resolveClass(Class<?> c) 連接指定的 Java 類。
複製代碼
/** * 1、ClassLoader加載類的順序 * 1.調用 findLoadedClass(String) 來檢查是否已經加載類。 * 2.在父類加載器上調用 loadClass 方法。若是父類加載器爲 null,則使用虛擬機的內置類加載器。 * 3.調用 findClass(String) 方法查找類。 * 2、實現本身的類加載器 * 1.獲取類的class文件的字節數組 * 2.將字節數組轉換爲Class類的實例 * @author lei 2011-9-1 */
public class ClassLoaderTest {
public static void main(String[] args) throws InstantiationException, IllegalAccessException, ClassNotFoundException {
//新建一個類加載器
MyClassLoader cl = new MyClassLoader("myClassLoader");
//加載類,獲得Class對象
Class<?> clazz = cl.loadClass("classloader.Animal");
//獲得類的實例
Animal animal=(Animal) clazz.newInstance();
animal.say();
}
}
複製代碼
class Animal{
public void say(){
System.out.println("hello world!");
}
}
複製代碼
class MyClassLoader extends ClassLoader {
//類加載器的名稱
private String name;
//類存放的路徑
private String path = "E:\\workspace\\Algorithm\\src";
MyClassLoader(String name) {
this.name = name;
}
MyClassLoader(ClassLoader parent, String name) {
super(parent);
this.name = name;
}
/** * 重寫findClass方法 */
@Override
public Class<?> findClass(String name) {
byte[] data = loadClassData(name);
return this.defineClass(name, data, 0, data.length);
}
public byte[] loadClassData(String name) {
try {
name = name.replace(".", "//");
FileInputStream is = new FileInputStream(new File(path + name + ".class"));
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int b = 0;
while ((b = is.read()) != -1) {
baos.write(b);
}
return baos.toByteArray();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
複製代碼
public class Test {
public static void main(String[] args) throws Exception{
ClassLoader classLoader=ClassLoader.getSystemClassLoader();
Class clazz=classLoader.loadClass("A");
System.out.print("Test");
clazz.forName("A");
}
}
class A{
static {
System.out.print("A");
}
}
複製代碼
解答:
public class Test {
public static void main(String[] args) {
System.out.print(B.c);
}
}
class A {
public static String c = "C";
static {
System.out.print("A");
}
}
class B extends A{
static {
System.out.print("B");
}
}
複製代碼
解答:
public class Test {
public static void main(String[] args) {
System.out.println(Test2.a);
}
}
class Test2{
public static final String a=new String("JD");
static {
System.out.print("OK");
}
}
複製代碼
解答:
java.lang.System
?答案:一般不能夠,但能夠採起另類方法達到這個需求。
解釋:爲了避免讓咱們寫System類,類加載採用委託機制,這樣能夠保證爸爸們優先,爸爸們能找到的類,兒子就沒有機會加載。而System
類是Bootstrap
加載器加載的,就算本身重寫,也老是使用Java
系統提供的System
,本身寫的System
類根本沒有機會獲得加載。 可是,咱們能夠本身定義一個類加載器來達到這個目的,爲了不雙親委託機制,這個類加載器也必須是特殊的。因爲系統自帶的三個類加載器都加載特定目錄下的類,若是咱們本身的類加載器放在一個特殊的目錄,那麼系統的加載器就沒法加載,也就是最終仍是由咱們本身的加載器加載。
引用參考:
① juejin.im/post/5a1d64…
② blog.csdn.net/boyupeng/ar…
③ blog.csdn.net/boyupeng/ar…
備註:文章內容還在深刻學習,會不斷修改跟新!