Java 27: Compact Object Headers

- Published on
- /9 mins read/
Việc thu nhỏ tiêu đề đối tượng từ 12-16 bytes xuống còn đúng 8 bytes (64 bits) trong HotSpot JVM không chỉ giúp ứng dụng tiết kiệm từ 10% đến 20% dung lượng Heap RAM, mà còn tối ưu hóa mật độ dữ liệu trên từng CPU Cache Line 64-byte của phần cứng.
# chi phí ẩn của Object Header trên 64-bit HotSpot JVM
Trong máy ảo HotSpot JVM 64-bit truyền thống, mỗi đối tượng (instance) được cấp phát trên bộ nhớ Heap đều bắt buộc phải mang theo một phần tiêu đề gọi là Object Header. Tiêu đề này được tạo thành từ hai thành phần cơ bản:
- Mark Word (64 bits / 8 bytes): Chứa thông tin trạng thái runtime của đối tượng:
- Identity Hash Code (31 bits).
- Độ tuổi phục vụ dọn rác (GC Age - 4 bits, tương ứng giá trị từ 0 đến 15).
- Trạng thái khóa đồng bộ (Lock Bits - 2 bits: Unlocked, Lightweight Locked, Inflated Monitor, Marked for GC).
- Cờ Epoch và Biased Locking metadata (trước khi bị loại bỏ).
- Klass Word (32 hoặc 64 bits): Con trỏ trỏ tới cấu trúc
Klasstrong vùng nhớ Metaspace để JVM xác định kiểu dữ liệu của đối tượng và bảng phương thức ảo (vtable). Khi bật cờ-XX:+UseCompressedClassPointers, con trỏ này chiếm 32 bits (4 bytes); nếu tắt, nó chiếm trọn 64 bits (8 bytes).
# hiệu ứng khuếch đại bộ nhớ (Memory Overhead)
Xét một đối tượng gói số nguyên phổ biến java.lang.Integer:
| Thành phần | Kích thước thực tế | Ghi chú |
|---|---|---|
| Mark Word | 8 bytes | Metadata runtime |
| Klass Word | 4 bytes | Compressed Class Pointer |
Giá trị value (int) | 4 bytes | Dữ liệu hữu ích duy nhất |
| Tổng trước đệm | 16 bytes | Đã vừa khớp bội số 8 bytes |
Một số nguyên 4 bytes cần tới 16 bytes bộ nhớ Heap để tồn tại. Tỷ lệ dữ liệu hữu ích (Payload Efficiency) chỉ đạt 25%, 75% còn lại là chi phí quản lý của JVM.
Với các cấu trúc dữ liệu đồ thị phức tạp, cây nhị phân, hoặc danh sách liên kết (LinkedList, HashMap.Node), hàng chục triệu node nhỏ khiến chi phí Object Header ngốn từ 20% đến 35% toàn bộ dung lượng Heap RAM.
# cấu trúc bit layout của Compact Object Headers (Project Lilliput)
Dự án Project Lilliput (JEP 450 trong OpenJDK) giải quyết bài toán này bằng cách nén toàn bộ thông tin của cả Mark Word và Klass Word vào một từ 64-bit duy nhất:
# 1. thay thế con trỏ trực tiếp bằng Compact Klass ID
Thay vì lưu một địa chỉ con trỏ thô (memory pointer) trỏ vào Metaspace, HotSpot JVM xây dựng một bảng mã hóa lớp (Class Table). Mỗi class được nạp vào bộ nhớ được gán một số định danh nguyên vẹn Klass ID nhỏ gọn:
- Sử dụng khoảng 22 đến 26 bits trong Mark Word.
- Một dải 22 bits cho phép định danh tới
2^22 ≈ 4,194,304lớp khác nhau, vượt xa số lượng class của bất kỳ ứng dụng doanh nghiệp hay microservice phức tạp nào. - Khi cần gọi phương thức ảo hoặc kiểm tra
instanceof, JVM thực hiện tra cứu theo chỉ mục bảng lớp (Table Lookup) với chi phí CPU gần nhưO(1).
# 2. xử lý xung đột Identity Hash Code và Khóa
Một thách thức kỹ thuật lớn là: Làm sao để nhồi vừa cả Hash Code 31-bit lẫn Klass ID 22-bit cùng lúc mà không làm phình 64 bits?
- JVM áp dụng cơ chế hoán vị trạng thái (Displaced Header): Khi đối tượng ở trạng thái chưa gọi
System.identityHashCode(), các bit này được dùng cho các mục đích tối ưu khác. - Khi đối tượng bị khóa nặng (Inflated Monitor Lock) hoặc được tính toán Hash Code đồng thời, HotSpot JVM sẽ đẩy (displace) thông tin đầy đủ vào cấu trúc
ObjectMonitorđược cấp phát ở vùng nhớ native ngoài Heap, giữ cho header 64-bit luôn nhất quán.
# tác động lên CPU Cache Line và phần cứng
Lợi ích của Compact Object Headers không dừng lại ở việc tiết kiệm dung lượng RAM thô. Cải tiến này tạo ra bước nhảy vọt về hiệu năng tính toán thông qua cấu trúc CPU Cache Line.
Các bộ vi xử lý hiện đại (x86-64 và ARM64) nạp dữ liệu từ RAM vào bộ nhớ đệm L1/L2/L3 theo từng khối cố định 64 bytes (Cache Line):
- Gia tăng mật độ dữ liệu (Data Density): Với một đối tượng có 2 trường
long(16 bytes payload):- Trước Java 27: 16 bytes header + 16 bytes payload = 32 bytes. Mỗi Cache Line 64-byte chỉ chứa được đúng 2 đối tượng.
- Với Java 27 Compact Headers: 8 bytes header + 16 bytes payload = 24 bytes. Mỗi Cache Line có thể nạp được gần 3 đối tượng.
- Giảm tỷ lệ CPU Cache Misses: Khi chương trình duyệt qua một mảng đối tượng (
Object[]), mật độ đối tượng cao hơn trên mỗi Cache Line đồng nghĩa với việc CPU ít phải truy xuất ra bộ nhớ ngoài (DRAM), giảm thiểu tình trạng nghẽn CPU Stall Cycles. - Giảm áp lực Garbage Collector: Khi kích thước Heap thực tế giảm từ 15% đến 25%, các thuật toán dọn rác như G1, ZGC hay Shenandoah có ít byte bộ nhớ hơn cần quét và di chuyển (evacuation), làm giảm đáng kể thời gian tạm dừng (Pause Times) và chi phí chu kỳ CPU của các tiến trình background GC threads.
# các cờ cấu hình HotSpot JVM và lưu ý triển khai
Để kích hoạt và theo dõi Compact Object Headers trên môi trường thực nghiệm và Production:
# cờ bật tính năng
# Kích hoạt Compact Object Headers
-XX:+UnlockExperimentalVMOptions -XX:+UseCompactObjectHeaders
# Đảm bảo tính tương thích với Compressed OOPs
-XX:+UseCompressedOops -XX:+UseCompressedClassPointers# tương thích với các thuật toán Garbage Collector
- G1 GC: Tương thích hoàn toàn. Quá trình nén và giải mã Klass ID được tích hợp trực tiếp vào quy trình sao chép vùng nhớ (Region Evacuation).
- Generational ZGC: Hỗ trợ đầy đủ các kỹ thuật Colored Pointers và Load Barrier song song với Compact Headers.
- Parallel GC: Hoạt động ổn định trên các batch processing workloads.
# đo lường thực tế với Java Object Layout (JOL)
Bạn có thể sử dụng thư viện org.openjdk.jol:jol-core để kiểm tra trực tiếp kích thước header của class trong ứng dụng:
import org.openjdk.jol.info.ClassLayout;
public class HeaderBenchmark {
static class UserSession {
private long userId; // 8 bytes
private int status; // 4 bytes
private boolean active; // 1 byte
}
public static void main(String[] args) {
System.out.println(ClassLayout.parseClass(UserSession.class).toPrintable());
}
}Kết quả in ra sẽ thể hiện rõ sự khác biệt: phần OFFSET của trường đầu tiên bắt đầu ngay tại byte thứ 8 (thay vì byte thứ 12 hoặc 16 như các phiên bản Java trước).
# bảng tổng kết so sánh kiến trúc
| Tiêu chuẩn kỹ thuật | Java 8 – 21 (Mặc định) | Java 27 (Compact Object Headers) |
|---|---|---|
| Kích thước Object Header | 12 bytes (với Compressed OOPs) hoặc 16 bytes | Đúng 8 bytes (64 bits) |
| Kích thước tối thiểu của một Object | 16 bytes (do padding bội số 8) | 8 bytes hoặc 16 bytes |
| Klass Identification | Con trỏ địa chỉ trực tiếp 32/64 bits | Klass ID 22-bit ánh xạ qua bảng lớp |
| Hiệu suất CPU Cache L1/L2 | Nhiều cache misses trên mảng đối tượng nhỏ | Mật độ dữ liệu cao hơn ~30% trên Cache Line |
| Mức tiết kiệm Heap trung bình | Mốc đối chiếu (Baseline) | Tiết kiệm 10% – 20% tổng Heap RAM |
| Thay đổi mã nguồn ứng dụng | Không | 0 dòng code (hoàn toàn trong suốt) |
Compact Object Headers trong Java 27 đại diện cho một bước chuyển mình mang tính nền tảng của HotSpot JVM, biến Java thành một nền tảng thực thi gọn nhẹ và tối ưu chi phí hạ tầng hàng đầu cho kỷ nguyên điện toán đám mây và microservices.
Chỉ là những ghi chép cá nhân với hy vọng mang lại chút giá trị. Nếu thấy hữu ích, đừng ngại chia sẻ cho bạn bè & đồng nghiệp nhé!
Happy coding 😎 👍🏻 🚀 🔥.
On this page
- # chi phí ẩn của Object Header trên 64-bit HotSpot JVM
- # hiệu ứng khuếch đại bộ nhớ (Memory Overhead)
- # cấu trúc bit layout của Compact Object Headers (Project Lilliput)
- # 1. thay thế con trỏ trực tiếp bằng Compact Klass ID
- # 2. xử lý xung đột Identity Hash Code và Khóa
- # tác động lên CPU Cache Line và phần cứng
- # các cờ cấu hình HotSpot JVM và lưu ý triển khai
- # cờ bật tính năng
- # tương thích với các thuật toán Garbage Collector
- # đo lường thực tế với Java Object Layout (JOL)
- # bảng tổng kết so sánh kiến trúc