1. 程式人生 > >new一個物件,java虛擬機器做了什麼?

new一個物件,java虛擬機器做了什麼?

這個問題一直困擾我很久,面試被問到好多次,但是都沒有一個清晰的認識,然後最近在回頭過來看下java基礎回味下。

當剛寫完一個java程式,main方法new一個物件,記憶體做了如下操作:
一、初期:
程式先進入編譯時期,java編譯器將程式程式碼編譯成位元組碼資訊;

二、中期—類載入和類連線

2.1類載入:首先通過類載入器類class檔案,載入到記憶體方法區,並建立java.lang.class類物件

2.2類連線:
- 驗證階段:檢驗類的結構是否正確
- 準備階段:對類的變數進行分配記憶體,並預設初始化
- 解析階段:將二進位制檔案的符號引用(任何形式的字面值)解析為直接引用

三、後期(類和物件初始化)
1. 如果new物件有引用變數指向它,棧記憶體存放引用變數指向null物件(未初始化,預設為null);
2. new一個物件時,jvm會在堆記憶體中開闢一個空間存放該物件;
3. 建立物件時,物件的成員(變數)先進行預設初始化;基本型別為基本型別預設值,引用型別為null,即引用變數的引用地址存放在棧(stack)記憶體中,物件的成員變數及值存放在堆記憶體
4. 物件成員初始化,對棧記憶體中的成員變數指定值;
第一步顯式初始化;
第二步構造程式碼塊初始化;
5. 建構函式初始化,new是建立一個物件,使用建構函式進行初始化物件(預設有一個)。

註明:

  • 在中期時,類載入指的是靜態成員變數和靜態程式碼塊;
  • 存在繼承時:
    原則:先靜後非,先父後子,先塊後器
    執行順序如下:
    第一步:父類靜態成員變數(方法區)
    第二步:父類靜態程式碼塊(多個按照順序執行)
    注意:根據靜態程式碼塊和變數位置順序初始化變數
    第三步:子類靜態成員變數(方法區)
    第四步:子類靜態程式碼塊
    第五步:父類成員變數和子類成員變數棧記憶體建立一片記憶體,指向值為null,先父類成員變數顯式初始化(如果有的話)
    第六步:父類程式碼塊(父類成員變數初始化
    第七步:父類構造器
    第八步:子類成員變數顯式初始化(如果有的話)
    第九步:子類程式碼塊(子類成員變數初始化

    第十步:子類構造器

例子分析:

class Base {
    private String name="base" ;
    public Base() {
        tellName();
        printName();
    }
    public void tellName() {
        System.out.println("Base tell name: " + name);
    }
    public void printName() {
        System.out.println("Base print name: " + name);
    }
}
public class Dervied extends Base {
    private String name = "dervied";
    public Dervied() {
        tellName();
        printName();
    }
    public void tellName() {
        System.out.println("Dervied tell name: " + name);
    }
    public void printName() {
        System.out.println("Dervied print name: " + name);
    }
    public static void main(String[] args){
        new Dervied();
    }
}

列印結果為:

Dervied tell name: null
Dervied print name:  null
Dervied tell name:  dervied
Dervied print name:  dervied

下來解釋這程式過程:

  1. 宣告父類成員變數name父=null,子類成員變數name=null;
  2. 顯示初始化父類成員變數,name父=“base”;
  3. 執行父類構造器,執行tellName();方法,此處省略this關鍵字,實際是this.tellName(),this指當前物件Dervied子類,子類的name值還沒有被初始化,所以預設為null,所以呼叫子類的tellName(),列印Dervied tell name: null,同理列印Dervied print name: null;
  4. 初始化子類成員變數name為dervied,所以列印
    Dervied tell name: dervied
    Dervied print name: dervied

這裡單獨分析類載入機制:APC

  1. All 全盤負責:當一個類載入器載入某個類時,全盤負責將其類依賴的類一併載入;
  2. Parent 父類委託:先父類載入器去試圖載入該class,當父類載入器無法載入該class時,才從自己的類路徑中載入該類;
  3. Cache 快取機制:快取機制會將所有曾經類載入器載入過的類存入快取中,當程式中需要某個類先去快取中查詢,如果查不到,則系統會讀取該類的class檔案繼續快取。這樣算是為什麼每次修改類後要重啟JVM的原因