JVM(八):Java 物件模型
本文將學習物件是如何建立的,物件的記憶體佈局,以及如何定位訪問一個物件。
物件建立
當虛擬機器碰到一個new指令時,首先檢查指令引數能否在常量池中定位一個類的符號引用,並且檢查該符號引用對應的類是否已經被載入,解析和初始化。當一切都確定完成後,JVM就會為其分配記憶體(需要分配的記憶體大小在現在就已經確定,在 下面 中詳細講述)。
物件的記憶體分配方式分為以下兩種:
- 指標碰撞,這種分配方式建立在堆內已用空間和剩餘空間是完整的,這樣的話,在兩者之間放置一個指標作為分界點的指示器即可,在分配空間時,只需要移到一下指標位置就好了。
- 空閒列表,如果 JVM 內的空間不是規整的,那麼就只能採用此方案了。此時 JVM 會維護一個列表,記錄了哪些記憶體塊是可用的,在分配的時候劃一個大小足夠的區域給物件例項,並更新列表即可。
以上兩種方式採取哪種,取決於 Java 堆是否工整,而堆是否工整又取決於垃圾回收演算法是否具有整理功能。
物件模型
前面說到物件在建立時就已經確定了記憶體大小,那麼 JVM 是怎麼確定物件的大小呢?物件在記憶體中又是如何儲存的呢?
在 JVM 中 Java 的物件模型分為以下3塊,物件頭,例項資料,對齊填充,下面就讓我們來分別介紹一下。
物件頭
物件頭的資料包括兩部分。一部分是用於儲存自身執行時資料,這部分資料被官方稱為“Mark World”。其中儲存資料包括Hashcode、GC 分代年齡、鎖狀態標誌、執行緒持有鎖、偏向執行緒ID、偏向時間戳等等。
物件頭的另外一部分是 型別指標,即物件指向其類後設資料的指標。通過這個指標,我們就可以知道該例項屬於哪個類。
例項資料
例項資料就是物件真正儲存的有效資訊,也就是程式碼中定義的各種型別的欄位內容,不論是父類的還是子類的,都需要記錄下來。其儲存順序受到虛擬機器分配策略和定義順序影響。
對齊填充
物件填充不是必要資料。在模型中只是起到佔位符的作用。因為 HotSpot 要求物件起始地址必須是8的整數倍,這樣在例項資料達不到要求的時候,就需要通過對齊填充來補齊。
物件訪問
物件訪問的方式是通過引用來定位、訪問。但 JVM 規範並沒有強制要求該通過何種方式使用引用,因此具體實現還是要依賴與具體虛擬機器型別。
不過目前的主流訪問方式就是以下兩種。
- 使用引用。其在 Java 堆中會獨立建立一個控制程式碼池,引用指向控制程式碼,而控制程式碼指向例項資料和型別資料。
使用這種方式來訪問的優點是穩定,例如在 GC 後,例項資料需要移動,那麼只需要修改控制程式碼池中的內容即可,reference 指向的是穩定的位置,缺點是這種方式需要二次定位,速度較慢。
- 直接指標訪問,引用直接堆中物件地址,堆中儲存了例項資料和型別資料指標,指標直接指向另外儲存的型別資料。
使用這種方式的優點是訪問例項資料快,因為 reference 指向直接的物件,省去了一次記憶體定位開銷。但缺點就是不夠穩定,在物件移動後,reference 也需要修改值。
具體採用何種,不同的虛擬機器有不同的實現,因為兩者各有千秋,並沒有強烈的優缺點,因此不同情況不同處理即可。
總結
在本文中介紹了物件的本質模型是什麼,以及物件是如何建立和訪問使用的,與上文的 JVM 記憶體模型結合來看,可以讓我們瞭解記憶體洩露產生的原因,有助於高效地理解使用 Java 的自動記憶體管理機制。
文章在公眾號“iceWang”第一手更新,有興趣的朋友可以關注公眾號,第一時間看到筆者分享的各項知識點。謝謝!筆芯!
本系列文章主要借鑑自《深入分析 Java Web 技術內幕》和《深入理解 Java 虛擬機器-JVM 高階特性與最佳實踐》。