https://mp.weixin.qq.com/s/y82t0a4dTBZwgY6MRnZDIw
創建對象的方式有4種:new 關鍵字、反射機制、Object 類的 clone 方法、反序列化。
針對 new 關鍵字的方式,來談談對象創建的過程,例如 Demo 類:
// 創建Demo類的實例對象 Demo demo = new Demo(); // 定義Demo類 public class Demo { private int i; private String str = "初始值"; // 構造代碼塊 { /* do something */ } // 靜態代碼塊 static { /* do something */ } // 構造方法 public Demo() { } public int getI() { return i; } public void setI(int i) { this.i = i; } public String getStr() { return str; } public void setStr(String str) { this.str = str; } }
創建對象的過程一共5個步驟,如下所示:
一、檢查類是否已經被加載
當 JVM 執行到 字節碼 new 指令時,首先會到 常量池 中定位到 Demo 類的符號引用,通過符號引用檢查 Demo 類是否已被加載。
如果 Demo 類沒有被被加載,就必須先執行加載過程。
二、為對象分配內存空間
Demo 類的加載檢查通過后,JVM 將會從堆中為 new Demo() 創建的實例對象分配確定大小的內存。
分配內存的方式有兩種:指針碰撞、空閑列表
-
指針碰撞
垃圾回收算法是標記-壓縮算法,堆內存的空間很整齊的,沒有碎片空間,使用中的內存放在一端,空閑的內存在另一端,在中間放置一個指針作為分界點。
分配內存時,只需要將指針往空閑內存的那一端挪動一段與對象大小的距離。
-
空閑列表
垃圾回收算法是標記-清除算法,堆內存的空間很凌亂的,存在碎片空間,使用的內存和空閑的內存相互交錯的在一起。
這種情況下,JVM 需要維護一個空閑列表,記錄哪些是空閑內存,分配內存的時候從空閑列表中找到一塊足夠大的內存空間存放對象,並更新空閑列表
三、將分配到的內存空間初始化零值(不包括對象頭)
為對象的字段進行初始化默認值,保證對象即使沒有賦初值也可以直接使用。
int 初始化為 0,boolean初始化為false,string 初始化為 null。
四、為對象進行必要的設置
設置對象頭(Object Head),包括這個對象所屬的類,類的元數據信息,對象的哈希碼,對象的 GC 分代年齡等信息。
五、執行構造方法
按照順序執行:
-
執行 Class 文件中的 <init>() 方法
-
初始化對象的字段:例如 str 初始化為 "初始值"
-
靜態代碼塊
-
構造代碼塊
-
構造方法
對象在堆內存中的存儲布局划分為三個部分:對象頭、實例數據、對齊填充。
對象頭包括兩部分信息:對象自身的運行時數據、類型指針。
一、對象頭
對象頭包括兩部分信息:對象自身的運行時數據、類型指針。
-
對象自身的運行時數據(Mark Word)
這些數據包括:哈希碼、GC 分代年齡、鎖狀態標志、線程持有鎖、偏向線程ID、偏向時間戳等。
這部分數據的長度在32位和64位的 JVM 中分別為:32 bit、64 bit。
-
類型指針
就是對象指向它的類型元數據的指針,可以理解為 demo 指向 Demo.class。
二、實例數據
實例數據就是對象中的所有字段內容,包括從父類繼承的所有字段內容。
例如 demo 中的 i 和 str 。
三、對齊填充
對齊填充並不是必然存在的,也沒有特別的含義,它僅僅是起到占位符的作用。
因為 JVM 存儲對象的內存大小必須是8字節的整數倍。
那么,當對象的大小不是8字節的整數倍,意味着對象申請到的內存大小,必然大於對象的大小,剩余的內存空間就是對齊填充。
對象的訪問定位
Demo demo = new Demo();
這一行代碼,demo 只是對象的引用,並不是真正創建的對象,demo 在內存中存儲的是 new Demo() 實例對象的地址。
demo 存儲在棧,new Demo() 所創建的實例對象存儲在堆。
demo 訪問 new Demo() 所創建的實例對象有兩種方式:句柄、直接指針。
一、句柄
句柄池存在堆,那么 demo 存儲的就是 new Demo() 的句柄地址,句柄中包含了對象的實例數據和類型數據的實際地址。
以 Demo 類為例,如下圖所示:
二、直接指針
直接指針 就是 demo 存儲的就是 new Demo() 的實際地址。
以 Demo 類為例,如下圖所示:
句柄 相比於 直接訪問 需要多一次間接訪問的開銷,不過句柄訪問最大的好處就是靈活,因為引用存儲的是句柄地址,句柄地址是不會發生改變的。
這相當於多了一層抽象,句柄就是這一層抽象。
這意味着,即使對象的實際地址發生改變,只需要修改句柄中的實例數據指針的地址,不需要修改引用的值,即 demo 存儲的值。
垃圾回收在使用 標記-壓縮算法 的情況下,對象的實際地址發生改變是十分普遍的。