編譯到運行java程序大體流程
Java虛擬機的基本結構及其內存分區:
JVM中把內存分爲直接內存、方法區、Java棧、Java堆、本地方法棧、PC寄存器等。
直接內存:就是原始的內存區
方法區:用於存放類、接口的元數據信息,加載進來的字節碼數據都存儲在方法區
Java棧:執行引擎運行字節碼時的運行時內存區,採用棧幀的形式保存每個方法的調用運行數據
本地方法棧:執行引擎調用本地方法時的運行時內存區
Java堆:運行時數據區,各種對象一般都存儲在堆上
PC寄存器:功能如同CPU中的PC寄存器,指示要執行的字節碼指令。
JVM的功能模塊主要包括類加載器、執行引擎和垃圾回收系統
類初始化
main方法 所在類會在執行前,先加載和類初始化。類初始化是從父類到子類自上到下
* 類初始化:先初始化父類,後子類初始
* 類初始化<clinit>():初始化類變量顯示賦值代碼、靜態代碼塊,次序是自上到下,
* <clinit>()只執行一次*
* 在編譯階段,編譯器收集所有的靜態字段的賦值語句及靜態代碼塊,
* 並按語句出現的順序拼接出一個類初始化方法<clinit>()。
* 此時,執行引擎會調用這個方法對靜態字段進行代碼中編寫的初始化操作。
1)類加載器會在指定的classpath中找到Son.class這個文件,然後讀取字節流中的數據,將其存儲在方法區中。
2)會根據Son.class的信息建立一個Class對象,這個對象比較特殊,一般也存放在方法區中,用於作爲運行時訪問Student類的各種數據的接口。
3)必要的驗證工作,格式、語義等
4)爲Son中的靜態字段分配內存空間,也是在方法區中,並進行零初始化,即數字類型初始化爲0,boolean初始化爲false,引用類型初始化爲null等。
在Son.java中只有一個靜態字段:
private static int j=test();
此時,並不會執行賦值爲test()返回值的操作,而是將其初始化爲0。
5)由於已經加載到內存了,所以原來字節碼文件中存放的部分方法、字段等的符號引用可以解析爲其在內存中的直接引用了,而不一定非要等到真正運行時才進行解析。
6)在編譯階段,編譯器收集所有的靜態字段的賦值語句及靜態代碼塊,並按語句出現的順序拼接出一個類初始化方法()。此時,執行引擎會調用這個方法對靜態字段進行代碼中編寫的初始化操作。
類實例初始
實例初始化<init>:先父類實例初始,後子類實例初始。
每個類實例初始過程:1.實例成員變量 、非靜態代碼塊是 按序執行
2. 最後是自身構造方法, 若子類構造函數指定了使用父類的構造函數,
則父類初始該指定的構造函數
<init>()方法,是編譯器將調用父類的<init>()的語句、構造代碼塊、實例字段賦值語句,
以及自己編寫的構造方法中的語句整合在一起生成的一個方法。保證調用父類的<init>()方法
在最開頭,自己編寫的構造方法語句在最後,而構造代碼塊及實例字段賦值語句按出現的順序
按序整合到<init>()方法中。
例子
/**
* 父類初始化<clinit>()
* 步驟1. j = method(); -> System.out.print("(5)");
* 步驟2. System.out.print("(1)");
* 父類實例初始化
* 步驟5. i = test();
* 步驟6.System.out.print("(3)"); -> System.out.print("(9)");
* 步驟7.System.out.print(i); //若不在子類中調有參的父類構造函數,則是默認的無參構造函數,System.out.print("(2)");
*/
class Father{
private int i = test();
private static int j = method();
static {
System.out.print("(1)");
}
public Father() {
System.out.print("(2)");
}
public Father(int i) {
System.out.print(i);
}
{
System.out.print("(3)");
}
public int test() {
System.out.print("(4)");
return 0;
}
public static int method() {
System.out.print("(5)");
return 0;
}
}
/**
*
* 子類初始化<clinit>()
* 步驟3. j = method(); -> System.out.print("(10)");
* 步驟4. System.out.print("(6)");
* 子類實例初始化
* 步驟8. i = test();-> System.out.print("(9)");
* 步驟9. System.out.print("(8)");
* 步驟10.System.out.print("(7)");
*
*/
public class Son extends Father{
private int i = test();
private static int j = method();
static {
System.out.print("(6)");
}
public Son() {
//super(); 第一行有一個默認的調用父類的無參構造方法
super(11); //顯示的調用父類的有參構造方法
System.out.print("(7)");
}
{
System.out.print("(8)");
}
public int test() {
System.out.print("(9)");
return 0;
}
public static int method() {
System.out.print("(10)");
return 0;
}
/**
* main方法 所在類會在執行前,先加載和類初始化。類初始化是從父類到子類自上到下
* 類初始化:先初始化父類,後子類初始
* 類初始化<clinit>():初始化類變量顯示賦值代碼、靜態代碼塊,次序是自上到下,<clinit>()只執行一次
*
*/
public static void main(String[] args) {
//實例初始化<init>:先父類實例初始,後子類實例初始。每個類實例初始過程:1.實例成員變量 、非靜態代碼塊是 按序執行
//2. 最後是自身構造方法, 若子類構造函數指定了使用父類的構造函數,則父類初始該指定的構造函數
//注意;父類在初始 i = test()時是調用的子類的test(),因當前對象是Son且Son類中重學了父類的test();
Son son1 = new Son();
System.out.println();
Son son2 = new Son();
}
}
//結果
//(5)(1)(10)(6)(9)(3)11(9)(8)(7)
//(9)(3)11(9)(8)(7)