在 Java 25 中減少物件頭大小並節省內存
1. 引言
本文將探討JEP 519及其為 Java 25 版本帶來的重大改進。這項名為「緊湊對象頭檔」(Compact Object Headers,簡稱 JEP)的提案旨在將緊湊對象頭檔從實驗性功能轉變為 HotSpot JVM 運行時中的一項完整產品特性。
JEP 450是 JEP 519 的前身,JEP 519 為 Java 24 引進了緊湊物件頭。 JEP 450 的緊湊物件頭特性在減少記憶體開銷和提高 Java 應用程式的效能方面發揮著至關重要的作用。
本文將主要關注 Java 中的自訂物件標頭及其影響。
2. 壓縮物件頭
JEP 519 在 JDK 25 中引入了緊湊物件頭作為替代物件頭佈局。該 JEP 的主要目標是減少 64 位元架構上 Hotspot JVM 中物件頭的大小。
2.1. 減小物件頭大小的動機
該 JEP 的主要目標是在 64 位元平台上將物件頭大小從 96 或 128 位元減少到 64 位元(8 位元組)。
對於平均大小為 256 到 512 位元的 Java 對象,物件頭佔用了超過 20% 的總記憶體空間。因此,這項 JEP 帶來了許多顯著優勢,例如:
- 減少堆大小
- 提高部署密度
- 提高資料本地性
- 減輕垃圾收集壓力
2.2 對象頭結構
在 HotSpot JVM 中,Java 物件在其記憶體佈局的開頭會強制包含一個頭部資訊。此頭部資訊由兩部分組成:標記字和類別指標。
標記詞是一個 64 位元字段,表示特定於實例的元數據,其包含以下內容:
- 物件的身份哈希碼
- 垃圾回收年齡和轉發指針訊息
- 鎖定和其他同步細節(同步程式碼區塊和方法中使用的監視器狀態)
我們可以從表格中看到這一點:
| 比特(63-0) | 大小(位) | 目的 |
|---|---|---|
| 63 – 38 | 26 | 未使用/已保留(可用於有偏鎖定紀元、物件位址等) |
| 37 – 7 | 31 | 身分哈希碼 |
| 6 – 3 | 4 | GC 年齡(由 G1 和平行 GC 等世代收藏家使用) |
| 2 – 0 | 3 | 鎖定狀態/標誌(對同步至關重要) |
在 64 位元系統中,JVM 將類別指標儲存為 32 位元壓縮引用,並使用它來定位元空間中類別的元資料。這種設計使得 JVM 能夠有效率地執行反射和類型檢查。
如果物件是基於數組的引用,則需要第三個欄位來提供數組長度資訊。
因此可以推斷,64 位元系統上的物件頭大小約為 96 位元到 128 位元。
3. 減小物件頭大小的技術實現
JEP 450 提出了一種新的緊湊型頭部佈局。此項變更旨在減小物件頭部的平均大小。
3.1. 緊湊型頁首佈局
緊湊型物件頭消除了標記字和類別指標之間的差異。它透過將類別指標以壓縮形式包含在標記字中來實現這一點。
因此,生成的 64 位元緊湊物件頭包含以下欄位:
- 壓縮類指針
- 哈希碼
- GC 年齡和標籤訊息,以及自行轉發的標籤
- 另有 4 位預留給瓦爾哈拉計劃未來使用。
若要使用緊湊型物件頭,應啟用壓縮類別指標。此外,這種緊湊佈局透過改變編碼方案,將類別指標的大小從 32 位元減少到 22 位元。
接下來,我們將深入探討此功能的技術實作細節。
3.2.鎖定更改
輕量級鎖定現在透過更改物件頭部中的幾個微小位元來指示鎖是已被佔用還是空閒。只有這些特定的位元會被更新,頭部中的其他任何內容都不會被更改或移動。
類似地,用於等待/通知場景的重型鎖(監視器)需要建立一個監視器結構,但僅調整頭部tag位元。所有其他位元在新佈局中都保持不變。
舊的堆疊鎖定方式已經過時了;緊湊物件頭不再支援這種舊機制。
3.3. GC轉發
熱點收集器( ZGC除外)現在透過將轉發指標寫入物件頭並僅保留一些頭來重新定位物件。
使用壓縮類別指標時,引入了新的編碼方式。在這種編碼方式下,自轉發不會破壞頭部數據,而是使用一個特定的位元來指示自轉發,轉發位址則位於低 42 位元中。
最後,必須保留每個頭部的全(滑動)GC 被重新設計,以利用新的打包方案。
3.4. 壓縮類指針
為了遵循緊湊型頭檔規範,壓縮類別指標 (CPC) 現在已成為強制性要求。這會將類別的數量限制在四百萬以下,但這對於所有 Java 應用程式來說已經足夠了。
有些 JIT 編譯器仍然不支援這種佈局,為了防止這種情況發生,它們會自動停用此功能。
4. 活化和益處
壓縮物件頭之前是一項實驗性功能,因此預設為停用。我們可以使用以下 JVM 標誌啟用壓縮物件頭:
-
-XX:+UnlockExperimentalVMOptions -
-XX:+UseCompactObjectHeaders.
JEP 519 將此功能從實驗階段移出。我們可以使用 JVM 標誌XX:+UseCompactObjectHeaders.
4.1 內存
此變更將物件頭大小縮小至 8 位元組。因此,JVM 可以顯著減少每個物件在堆中的佔用空間。
對於包含大量小物件的應用程序,其總記憶體佔用量可減少 10-20%,這主要得益於即時資料所用記憶體的減少。因此,更低的記憶體消耗意味著更高的硬體效率、垃圾回收效率,以及在雲端和容器化環境中更佳的部署密度。
4.2. 降低氣相層析壓力
緊湊的物件頭結構能夠減少相同 Java 工作負載下的堆記憶體使用量,從而降低垃圾回收的頻率並加快速度。這也有助於減少 GC 活動,進而降低延遲並提高吞吐量,尤其是在記憶體分配密集型工作負載中。
4.3 資料本地性
資料局部性用於定義記憶體中相關資料片段的放置順序。 JVM 利用資料局部性來優化記憶體效率。
透過新的物件頭佈局,物件的整體大小得以減少。這提高了快取行的利用率和資料局部性,因為單一 CPU 快取行可以容納更多物件。因此,JVM 可以更有效率地遍歷、操作和移動對象,減少快取未命中次數。
5. 風險與測試
由於頭部位密度增加以及採用了新的頭部編碼,未來的 JVM 功能可能會出現頭部位不足的情況。此外,該功能還需要進行大量的測試和基準測試,以驗證其在並發負載下的可靠性和效能。另外,x64 上的 JVMCI 不支援這種緊湊的頭部結構。啟用 JVMCI 時,可以透過關閉該功能來降低風險。
長期風險在於,如果 JVMCI 始終不實現對緊湊頭檔的支持,這將永遠阻礙舊版頭檔實現的遷移。一些直接操作物件頭檔的元件,特別是作為 JVMCI 主要使用者的 Graal 編譯器,將不得不實現新的頭檔佈局。因此,在這種情況下,該功能將被停用。
最後,我們將討論專案失敗的風險。雖然可能性極小,但此特性有可能與舊版頭結構相比出現無法彌補的功能退化。這可能是由於可表示類別的數量總體減少所致。另一種可能性極小的情況是,精簡的物件頭無法帶來實際的改進,或者升級帶來的複雜性增加不足以彌補其帶來的收益。
6. 自訂物件標頭的基準測試結果
雖然目前還沒有預設啟用此物件頭實現的計劃,但一些基準測試結果表明了該特性的優勢和好處:
- 諸如 SPECjbb2015 之類的基準測試表明,在一種設定下,堆空間減少了 22%,CPU 時間減少了 8%。
- 在另一種情況下,SPECjbb2015 在與 G1 或平行收集器(不支援 ZGC)運作時,垃圾回收次數減少了 15%。
- 高度並行化的 JSON 解析器基準測試運行時間縮短了 10%。
7. 結論
本文深入探討了 JEP 519 的緊湊型物件頭特性。此特性旨在透過減小物件頭的大小,顯著提升性能並提高空間利用率。
我們也深入探討了該特性的內部實作方式,以及對鎖定和垃圾回收轉發頭資訊所做的修改。總而言之,該特性使 JVM 能夠面向未來。