整理自《深入理解 Java 虛擬機》。
1. 對象的創建
虛擬機遇到一條 new 指令時,將執行以下過程:
- 檢查這個指令的參數是否能在常量池中定位到一個類的符號引用,並且檢查這個符號引用代表的類是否已經被加載、解析和初始化過。如果沒有,必須先執行相應的類加載過程。
- 分配內存。對象所需內存的大小在類加載完成後便可完全確定,爲對象分配空間的任務等同於把一塊確定大小的內存從 Java 堆中劃分出來。根據採用的不同的垃圾收集器,採取的分配方式爲“指針碰撞”或“空閒列表”。
- 將分配的內存空間都初始化爲零值(不包括對象頭)。
- 對對象頭進行設置。包括這個對象是哪個類的實例、如何才能找到類的元數據信息、對象的哈希碼、對象的 GC 分代年齡等信息。
- 執行 < init > 方法,把對象按照程序員的意願進行初始化。
2. 對象的內存佈局
對象頭:
markWord:用幹存儲對象自身的運行時數據,如哈希碼 (HashCode) 、 GC 分代年齡、鎖狀態標誌、線程持有的鎖、偏向線程 ID 、偏向時間戳等。
類元數據指針:對象指向它的類元數據的指針,虛擬機通過這個指針來確定這個對象是哪個類的實例。
實例數據部分:對象真正存儲的有效信息,也是在程序代碼中所定義的各種類型的字段內容。
對齊填充:並不是必然存在的,也沒有特別的含義,它僅僅起着佔位符的作用。
3. 對象的訪問定位
Java 程序需要通過棧上的 reference 數據來操作堆上的具體對象。兩種方式:
使用句柄訪問:
Java 堆中劃分出一塊內存作爲句柄池,reference 中存儲的就是對象的句柄地址,句柄中包含了對象實例數據與類型數據各自的具體地址信息。
使用句柄的好處是 reference 中儲存的是穩定的句柄地址,在對象被移動(垃圾收集時移動對象是非常普遍的行爲)時只會改變句柄中的實例數據指針,而 reference 本身不需要修改。
使用直接指針訪問:
Java堆對象的佈局必須考慮如何放置訪問類型數據的相關信息,而 reference 中儲存的直接就是對象地址。
使用直接指針的最大好處就是速度更快,它節省了一次指針定位的時間開銷,由於對象的訪問在 Java 中非常頻繁,因此這類開銷積少成多後也是一項非常可觀的執行成本。