TungDaDev's Blog

Java 27: Compact Object Headers

Java 27 compact headers.webp
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:

  1. 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ỏ).
  2. Klass Word (32 hoặc 64 bits): Con trỏ trỏ tới cấu trúc Klass trong 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ầnKích thước thực tếGhi chú
Mark Word8 bytesMetadata runtime
Klass Word4 bytesCompressed Class Pointer
Giá trị value (int)4 bytesDữ liệu hữu ích duy nhất
Tổng trước đệm16 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,304 lớ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):

  1. 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.
  2. 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.
  3. 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ậtJava 8 – 21 (Mặc định)Java 27 (Compact Object Headers)
Kích thước Object Header12 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 Object16 bytes (do padding bội số 8)8 bytes hoặc 16 bytes
Klass IdentificationCon trỏ địa chỉ trực tiếp 32/64 bitsKlass ID 22-bit ánh xạ qua bảng lớp
Hiệu suất CPU Cache L1/L2Nhiề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ìnhMốc đối chiếu (Baseline)Tiết kiệm 10% – 20% tổng Heap RAM
Thay đổi mã nguồn ứng dụngKhông0 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 😎 👍🏻 🚀 🔥.