JVM工作原理-類成員的載入和執行過程
JVM工作原理-類成員的載入和執行過程
jvm可以執行位元組碼檔案,當我們通過jvm執行一個java位元組碼檔案時,jvm首先在記憶體中開闢一塊jvm記憶體,專門用於執行java位元組碼檔案,另外jvm會通過一個名為類載入器(Class Loader)這麼一個角色將電腦硬碟上位元組碼檔案載入到jvm記憶體中,然後在jvm記憶體中執行位元組碼檔案中記錄的資訊(程式)。此時我們編寫的java原始檔才真正的在記憶體中執行起來。
注意:jvm記憶體就是我們經常說的棧記憶體、堆記憶體、方法區(常量池、方法區、靜態元素區)的一個總稱。
如下圖:
現在我們知道了在我們通過java.exe執行工具執行位元組碼檔案時,jvm會自動的將這個位元組碼檔案通過類載入器Class Loader
類成員的載入和執行過程
位元組碼檔案中包含的資訊有:類名、類成員(屬性、方法)。那麼研究位元組碼檔案的執行過程無非就是研究位元組碼檔案中的類成員的載入和執行過程。我們知道一個類的類成員大致可以分為屬性和方法,但是細分可以分為一般屬性、一般方法、構造程式碼塊、構造方法、靜態屬性、靜態方法、靜態程式碼塊,那麼接下來的探討可以分為三個部分:
- 屬性和方法的載入位置。
- 屬性和方法的載入順序。
- 構造方法、構造程式碼塊、靜態程式碼塊的執行順序。
屬性和方法載入位置:
一般屬性、一般方法、構造程式碼塊、構造方法載入到方法區中,而靜態屬性、靜態方法、靜態程式碼塊則儲存在方法區中靜態元素區中。如下圖:
屬性和方法的載入順序:
依次載入:一般屬性、一般方法、構造程式碼塊、構造方法、靜態元素、靜態方法、靜態程式碼塊。
如下圖:
當我們通過new的方式建立一個Person物件時,就會在堆記憶體中開闢一塊記憶體空間,將Person類模板上的一般屬性載入到這塊記憶體上,並新增了一個this屬性用於指向這塊記憶體自己本身,此外這塊記憶體空間儲存著其對應的類。如下圖:
注意:這個圖還存在一些瑕疵,因為這個記憶體圖沒有暫時還沒有考慮到Person類的父類的載入過程。
構造方法、構造程式碼塊、靜態程式碼塊的執行順序:
當我們通過new的方式建立一個Person物件時,需要呼叫Person類的構造方法,在呼叫構造方法之前,我們需要將Person.class位元組碼檔案載入到jvm記憶體中的方法區中,此時我們才能找到Person類的構造方法並呼叫,但是需要注意的是,此時Person中的屬性和方法都必須載入到方法區中才能訪問,而靜態程式碼塊一旦載入完畢之後就會立即執行,所以在呼叫構造方法之前,靜態程式碼塊就已經執行一次且僅一次。當靜態程式碼塊執行完畢之後,我們才能呼叫構造方法,而在呼叫構造方法之前,構造程式碼塊需要執行一遍。
綜上:靜態程式碼塊先於構造程式碼塊執行,構造程式碼塊先於構造方法執行。
測試:
public class Person {
private String field = "我是person中的一般屬性";
private static String staticField = "我是person中的靜態屬性";
public Person(){
super();
System.out.println("我是person中的預設無引數構造方法");
}
public void method() {
System.out.println("我是person中的一般方法");
}
public static void staticMethod(){
System.out.println("我是person中的靜態方法");
}
{
System.out.println(Person.staticField);
Person.staticMethod();
System.out.println(this.field);
this.method();
System.out.println("我是person中的構造程式碼塊");
}
static{
System.out.println(Person.staticField);
Person.staticMethod();
System.out.println("我是person中的靜態程式碼塊");
}
}
public class TestMain {
public static void main(String[] args) {
Person p = new Person();
}
}
測試結果:
我是person中的靜態屬性
我是person中的靜態方法
我是person中的靜態程式碼塊
我是person中的靜態屬性
我是person中的靜態方法
我是person中的一般屬性
我是person中的一般方法
我是person中的構造程式碼塊
我是person中的預設無引數構造方法
列印結果解讀:
當我們通過new的方式建立Person物件時,會呼叫Person類的構造方法,通過上面的分析我們可以知道,靜態程式碼塊先於構造程式碼塊執行,構造程式碼塊先於構造方法執行。而類成員的載入順序:一般屬性、一般方法、構造程式碼塊、構造方法、靜態屬性、靜態方法、靜態程式碼塊,所以靜態程式碼塊執行的時候可以訪問到靜態元素和靜態方法,而構造程式碼塊執行的時候可以訪問到靜態屬性、靜態方法、一般屬性、一般方法,最後是構造方法的執行。
注意:靜態元素不能訪問非靜態元素(一對多,不明確訪問哪個物件的屬性或者方法),而非靜態元素可以訪問靜態元素(多對一,靜態元素有且只有一份,明確訪問)。
綜上可得到類物件的建立過程(未考慮父類的情況):
- 載入類的一般屬性、一般方法、構造程式碼塊、構造方法。
- 開闢一塊靜態元素空間,載入類的靜態屬性、靜態方法、靜態程式碼塊。
- 執行類的靜態程式碼塊。
- 在堆記憶體中開闢一塊空間,用於表示該類的物件。
- 在該物件空間內載入類的一般屬性,this屬性,持有類的資訊。
- 執行物件的構造程式碼塊。
- 執行物件的構造方法。
- 複製一份物件空間中的this屬性值給棧記憶體中的變數。
存在繼承關係的類成員載入執行過程
上文我們忽略當前類的父類的載入過程,接下來我們來探討一下父類的載入過程對當前類的載入過程的影響。
引入了父類的載入過程,無非就是在在載入子類屬性或者方法之前先載入父類的屬性和方法,這好比先有父親再有兒子的關係,其實程式設計的思維和我們現實生活中的考慮問題的思維很像。
存在繼承關係的子類物件建立過程:
- 載入父類的一般屬性、一般方法、構造程式碼塊、構造方法。
- 開闢一塊屬於父類的靜態元素空間,載入靜態屬性、靜態方法、靜態程式碼塊。
- 執行父類的靜態程式碼塊。
- 載入子類的一般屬性、一般方法、構造程式碼塊、構造方法。
- 開闢一塊屬於子類的靜態元素空間,載入靜態屬性、靜態方法、靜態程式碼塊。
- 執行子類的靜態程式碼塊。
- 在堆記憶體中開闢一塊子類物件空間。
- 在子類物件空間中開闢一塊名為super的父類物件空間,並在這塊空間中載入父類的一般屬性、持有類的資訊。
- 執行父類的構造程式碼塊。
- 執行父類的構造方法。
- 在子類物件空間中載入子類的一般屬性、this屬性、持有類的資訊。
- 執行子類的構造程式碼塊。
- 執行子類的構造方法。
- 將子類物件空間中的this屬性值複製一份給棧記憶體中的子類型別變數。
過程圖如下:
測試:
public class Person {
private String field = "我是person中的一般屬性";
private static String staticField = "我是person中的靜態屬性";
public Person(){
super();
System.out.println("我是person中的預設無引數構造方法");
}
public void studentMethod() {
System.out.println("我是person中的一般方法");
}
public static void staticMethod(){
System.out.println("我是person中的靜態方法");
}
{
System.out.println(Person.staticField);
Person.staticMethod();
System.out.println(this.field);
this.studentMethod();
System.out.println("我是person中的構造程式碼塊");
}
static{
System.out.println(Person.staticField);
Person.staticMethod();
System.out.println("我是person中的靜態程式碼塊");
}
}
public class Student extends Person {
private String field = "我是student中的一般屬性";
private static String staticField = "我是student中的靜態屬性";
public Student() {
super();
System.out.println("我是student中的預設無引數構造方法");
}
public void method() {
System.out.println("我是student中的一般方法");
}
public static void staticMethod() {
System.out.println("我是student中的靜態方法");
}
{
System.out.println(Student.staticField);
Student.staticMethod();
System.out.println(this.field);
this.method();
System.out.println("我是student中的構造程式碼塊");
}
static{
System.out.println(Student.staticField);
Student.staticMethod();
System.out.println("我是student中的靜態程式碼塊");
}
}
public class TestMain {
public static void main(String[] args) {
Student s = new Student();
}
}
測試結果:
我是person中的靜態屬性
我是person中的靜態方法
我是person中的靜態程式碼塊
我是student中的靜態屬性
我是student中的靜態方法
我是student中的靜態程式碼塊
我是person中的靜態屬性
我是person中的靜態方法
我是person中的一般屬性
我是person中的一般方法
我是person中的構造程式碼塊
我是person中的預設無引數構造方法
我是student中的靜態屬性
我是student中的靜態方法
我是student中的一般屬性
我是student中的一般方法
我是student中的構造程式碼塊
我是student中的預設無引數構造方法