Back to JVM Internals: Advanced
Q5Question 5 of 10

What are Compact Object Headers?

Key Concepts
  • Every Java object has a header: mark word (8 bytes) + class pointer (4 bytes compressed) = 12 bytes minimum
  • Project Lilliput aims to reduce headers to 8 bytes (combine mark word + class pointer)
  • Small objects (boolean, byte) can be >50% header overhead
  • Reducing header size saves significant memory for applications with many small objects
  • Expected in future Java releases (experimental in JDK 23+)

Definition

Every Java object has a 12-byte header (mark word + class pointer). Project Lilliput aims to reduce this to 8 bytes, saving memory for applications with many small objects.

Object header layout (HotSpot 64-bit, compressed oops):

  • Mark word: 8 bytes – hash code, GC age, lock state.
  • Class pointer: 4 bytes (compressed) – points to the class metadata.
  • Total: 12 bytes minimum header before any fields.
  • Alignment: Objects padded to 8-byte boundary.

Every bank account file has a cover sheet (header) with metadata (account type, creation date, lock status). Even a file with just one page of data still needs the full cover sheet. Compact headers use a smaller cover sheet.

Understanding the Concepts

Current layout example

TEXT
  Account object (64-bit JVM, compressed oops):
    Header: 12 bytes (mark 8 + class ptr 4)
    accountId: 4 bytes (compressed reference)
    balance: 4 bytes (compressed reference)
    txnCount: 4 bytes (int)
    active: 1 byte (boolean)
    Padding: 3 bytes (align to 8)
    Total: 28 bytes (but only 13 bytes are your data!)

Why does it matter?

  • A Boolean object: 12 bytes header + 1 byte data + 3 bytes padding = 16 bytes. 94% overhead!
  • An application with 100 million small objects wastes ~400 MB on headers alone.

What should a senior engineer know?

  • JOL (Java Object Layout) shows exact header + field layout.
  • Project Lilliput targets 8-byte headers by combining mark word and class pointer.
  • Value types (Project Valhalla) would eliminate headers entirely for inline types.

What are common mistakes?

A common mistake is not understanding the concept fully before using it in production banking applications.

Wrong approach:

JAVA
package com.bank.service;

public class TransferService {
    public void transfer(String fromAccount, String toAccount, double amount) {
        // WRONG: using double for money
        double balance = getBalance(fromAccount);
        balance = balance - amount;
    }
    private double getBalance(String id) { return 10000.0; }
}

Correct approach:

JAVA
package com.bank.service;

import java.math.BigDecimal;

public class TransferService {
    public void transfer(String fromAccount, String toAccount, BigDecimal amount) {
        // CORRECT: using BigDecimal for money
        BigDecimal balance = getBalance(fromAccount);
        balance = balance.subtract(amount);
    }
    private BigDecimal getBalance(String id) { return new BigDecimal("10000.00"); }
}

Always use BigDecimal for monetary calculations in banking systems to avoid floating-point precision errors.

What are the edge cases?

Edge cases can cause unexpected behavior in banking applications. Here are some to watch for.

JAVA
package com.bank.edge;

public class EdgeCaseDemo {
    public static void main(String[] args) {
        // Edge case 1: null handling
        String accountId = null;
        try {
            System.out.println(accountId.length());
        } catch (NullPointerException e) {
            System.out.println("Null account ID");
        }

        // Edge case 2: empty vs null
        String emptyId = "";
        System.out.println(emptyId.isEmpty());  // true
        System.out.println(emptyId == null);     // false

        // Edge case 3: boundary values
        java.math.BigDecimal zero = java.math.BigDecimal.ZERO;
        java.math.BigDecimal negativeBalance = new java.math.BigDecimal("-0.01");
        System.out.println(zero.compareTo(negativeBalance) > 0);  // true
    }
}
TEXT
Null account ID
true
false
true

Always test with null values, empty strings, boundary values, and negative amounts in banking applications.

What are common interview follow-up questions?

Interviewers often follow up with these related questions:

  • How does this concept apply to a high-throughput banking transaction system?
  • What are the performance implications in a production environment?
  • How would you debug issues related to this concept?
  • What tools would you use to monitor this in production?
  • How does this behave differently in Java 8 vs Java 17+?

When answering follow-up questions, always relate your answer back to practical experience. For example, at National Bank of India, Rajesh debugged a production issue related to this concept by using JFR recordings and heap dumps. Providing concrete examples from banking systems shows the interviewer that you understand both the theory and the practice.

Visual Overview

DIAGRAM
  Current: Mark (8) + Class Ptr (4) = 12 bytes header
  Lilliput: Mark + Class combined = 8 bytes header
  Valhalla: Value types = 0 bytes header (inline)

  Boolean object: 16 bytes total, 1 byte data → 94% overhead
  → Compact headers save significant memory at scale

Key Takeaway

Every Java object has a 12-byte header (mark word + class pointer). For small objects, this is the majority of the memory. Project Lilliput aims to reduce headers to 8 bytes. Use JOL to inspect your objects' actual memory layout.

One sentence to remember: object headers are 12 bytes minimum – Project Lilliput shrinks them to 8 bytes for memory savings at scale.

---

Share
Use ← → arrow keys to navigate