Thursday, May 28, 2026

Java 26: Lazy Constants

In my previous post, I wrote about Stable Values introduced in Java 25. Java 26 renames them from Stable Values to Lazy Constants. While the underlying idea remains the same, the new name better reflects the intended use case: immutable values that are initialised lazily. This is a preview language feature.

A Lazy Constant allows you to defer the initialisation of immutable data until it is actually needed, while still allowing the JVM to optimise access to that data as though it were a regular final field.

Here is the example from the previous post, rewritten using a LazyConstant:

public class Controller {

    private final LazyConstant<ExpensiveResource> resource =
            LazyConstant.of(() -> new ExpensiveResource());

    public void process(String request) {
        resource.get().get(request);
    }
}

Initially, the lazy constant is uninitialised. The first call to resource.get() invokes the lambda expression, creates the ExpensiveResource, stores it permanently, and returns it. Subsequent calls simply return the already initialised value. Importantly, the initialisation function is guaranteed to execute only once, even under concurrent access.

Under the hood, the content of a LazyConstant is stored in a non-final field annotated with the JDK-internal @Stable annotation. This tells the JVM that the field will never change after it is written. Due to this guarantee, the JVM can treat the value like a constant, provided that the reference to the stable value is final, and perform constant-folding optimisations, even through multiple layers of stable values.

Thursday, January 01, 2026

fahd.blog in 2025

Happy 2026, everyone!

I'd like to wish everyone a great start to an even greater new year!

In keeping with tradition, here's one last look back at fahd.blog in 2025.

During 2025, I posted 8 new entries on fahd.blog. I am also thrilled that I have more readers from all over the world! Thanks for reading and especially for giving feedback.

Top 3 posts of 2025:

I'm going to be writing a lot more this year, so stay tuned for more great techie tips, tricks and hacks! :)

Related posts:

Friday, December 26, 2025

Java 25: Stable Values

Java 25 introduces Stable Values, which are objects that hold immutable data. They let you initialise immutable fields lazily, while still allowing the JVM to treat them as constants, and thus perform the same optimisations (such as constant-folding) that are done for final fields. This is a preview language feature.

Consider the following example:

public class Controller {
    private final ExpensiveResource resource = new ExpensiveResource();
    
    public void process(String request) {
        resource.get(request);
    }
}

The problem here is that, since resource is a final field, it must be initialised eagerly, which means creating a Controller can be slow. It's also unnecessary to create the expensive resource if the process method is never called during the runtime of the application. In order to "defer immutability" and lazily initialise fields, we have to use complex workarounds such as the class-holder idiom, as shown below:

public class Controller {
    public static ExpensiveResource getResource() {
        class Holder {
            private static final ExpensiveResource RESOURCE =
                    new ExpensiveResource();
        }
        return Holder.RESOURCE;
    }

    public void process(String request) {
        getResource().get(request);
    }
}

This is where Stable Values come in.

Here is the same class, rewritten using a StableVaue:

public class Controller {
    private final StableValue<ExpensiveResource> resource = StableValue.of();

    public ExpensiveResource getResource() {
        return resource.orElseSet(() -> new ExpensiveResource());
    }

    public void process(String request) {
        getResource().get(request);
    }
}

Initially, the stable value holds no content. When the orElseSet method is invoked for the first time, the expensive resource is initialised and set into the stable value, and subsequent calls will simply return it. The orElseSet method guarantees that the provided lambda expression is evaluated only once, even when it is invoked concurrently.

A more convenient way to use stable values is via a Supplier instead, as shown below:

public class Controller {
    private final Supplier<ExpensiveResource> resource = 
        StableValue.supplier(() -> new ExpensiveResource());

    public void process(String request) {
        resource.get().get(request);
    }
}

Using a stable value supplier, rather than a stable value, is more readable because the declaration and initialisation of the resource field are now together.

Under the hood, a stable value is a non-final field annotated with the JDK-internal @Stable annotation. This tells the JVM that the field will never change after it is written. Due to this guarantee, the JVM can treat the value like a constant, provided that the reference to the stable value is final, and perform constant-folding optimisations, even through multiple layers of stable values.

Monday, December 22, 2025

Java 25: Compact Object Headers

Java 25 introduces Compact Object Headers, an optimisation that reduces the memory overhead of Java objects.

In my previous post, I wrote about how you can measure the size of java objects using JOL, and inspect the size of the object header. For example, take the following class:

public class Point {
  int x;
  int y;
}

Use JOL to inspect its layout:

import org.openjdk.jol.info.ClassLayout;

public class JolExample {
  public static void main(String[] args) {
    System.out.println(ClassLayout.parseClass(Point.class).toPrintable());
  }
}

The output is:

Point object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     N/A
  8   4        (object header: class)    N/A
 12   4    int Point.x                   N/A
 16   4    int Point.y                   N/A
 20   4        (object alignment gap)    
Instance size: 24 bytes

This shows that even though the Point class only has 2 int fields requiring a total of 8 bytes, the actual object uses three times that amount (24 bytes), due to the object header (12 bytes) and alignment (4 bytes).

Now let's turn on Compact Object Headers using the following JVM flag:

-XX:+UseCompactObjectHeaders

Rerunning JOL, outputs the following:

Point object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     N/A
  8   4    int Point.x                   N/A
 12   4    int Point.y                   N/A
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total

As shown above, with compact object headers enabled, the object header now takes 8 bytes instead of 12, a saving of 4 bytes.

Previously, the object header layout was split into a mark word (8 bytes) and a class word (4 bytes). With compact object headers, the division between the mark and class words is removed, and the class word is subsumed into the mark word for a total of 8 bytes.

Thursday, December 18, 2025

Measuring Java Object Size with JOL (Java Object Layout)

JOL (Java Object Layout) is a small but powerful tool developed by the OpenJDK team that lets you inspect and measure how Java objects are actually laid out in memory.

With JOL, you can:

  • Inspect object headers and field offsets
  • See padding and alignment effects
  • Measure shallow and deep object sizes
  • Compare layouts across JVM configurations

Let's start with the following simple class:

public class Point {
  int x;
  int y;
}

Now use JOL to inspect its layout using ClassLayout:

import org.openjdk.jol.info.ClassLayout;

public class JolExample {
  public static void main(String[] args) {
    System.out.println(ClassLayout.parseClass(Point.class).toPrintable());
  }
}

The output is:

Point object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     N/A
  8   4        (object header: class)    N/A
 12   4    int Point.x                   N/A
 16   4    int Point.y                   N/A
 20   4        (object alignment gap)    
Instance size: 24 bytes

This shows that even though the Point class only has 2 int fields requiring a total of 8 bytes, the actual object uses three times that amount (24 bytes), due to the object header (12 bytes) and alignment (4 bytes).

Shallow Size vs. Deep Size

The shallow size is the memory consumed by the object itself, excluding objects it references i.e. it includes the fields, object header and padding, but not referenced objects.

The deep size, on the other hand, includes the entire object graph reachable from the object.

To demonstrate this, let's look at the following example:

public class Address {
  private final String city;

  public Address(String city) {
    this.city = city;
  }
}

public class Person {
  private final String name;
  private final Address address;
  private final int age;

  public Person(String name, Address address, int age) {
    this.name = name;
    this.address = address;
    this.age = age;
  }
}

Here is the JOL output, which shows the shallow size of the Address and Person:

> ClassLayout.parseClass(Address.class).toPrintable();

Address object internals:
OFF  SZ               TYPE DESCRIPTION               VALUE
  0   8                    (object header: mark)     N/A
  8   4                    (object header: class)    N/A
 12   4   java.lang.String Address.city              N/A
Instance size: 16 bytes

> ClassLayout.parseClass(Person.class).toPrintable();

Person object internals:
OFF  SZ               TYPE DESCRIPTION               VALUE
  0   8                    (object header: mark)     N/A
  8   4                    (object header: class)    N/A
 12   4                int Person.age                N/A
 16   4   java.lang.String Person.name               N/A
 20   4            Address Person.address            N/A
Instance size: 24 bytes

As shown above, the Person's shallow size includes the object header, age and object references (name and address), but does not include the String object for name, Address object, String inside Address, or any backing char[] or byte[] arrays.

To see the deep size of the Person, use GraphLayout instead of ClassLayout, like this:

import org.openjdk.jol.info.GraphLayout;

public class JolExample {
  public static void main(String[] args) {
    final Address address = new Address("London");
    final Person person = new Person("Alice", address, 30);
    System.out.println(GraphLayout.parseInstance(person).toFootprint());        
  }
}

The output is:

Person@27abe2cdd footprint:
     COUNT       AVG       SUM   DESCRIPTION
         2        24        48   [B
         1        16        16   Address
         1        24        24   Person
         2        24        48   java.lang.String
         6                 136   (total)

That's 136 bytes in total. Note that each String is backed by a byte array (represented by [B) which is 24 bytes.

Therefore, Person is only 24 bytes shallow, but costs 136 bytes deep.