TLABs: allocation is a pointer bump

JVM · lesson 21 of 34 · 3 min read

Each thread owns a private slab of eden, so new is an add and a store, with no lock anywhere.

Open this lesson in the learning hub

Key points

  • Eden is handed out in TLABs: every thread gets a private chunk, so two threads never contend on the same allocation pointer.
  • Allocating inside a TLAB is one pointer bump plus zeroing the object. It is a handful of instructions, not a call into the VM.
  • When a TLAB runs out the thread claims a fresh one from eden. Its size adapts to how fast that particular thread allocates.
  • Objects too big for a TLAB go straight into shared eden, and in G1 anything over half a region becomes a humongous old-gen object.
  • So allocation rate, not allocation cost, is what shows up in a profile. The bill arrives at the next young collection.
  • Escape analysis can delete the allocation altogether, which is the only thing cheaper than a pointer bump.

Example

import java.lang.management.GarbageCollectorMXBean;
import java.lang.management.ManagementFactory;

public class Main {

    static class Order { int id; long total; }

    static Object sink;                      // published, so the JIT cannot delete the allocation

    public static void main(String[] args) {
        for (int i = 0; i < 200_000; i++) { sink = new Order(); }      // let the JIT warm up

        long gcBefore = collections();
        int n = 20_000_000;
        long t0 = System.nanoTime();
        for (int i = 0; i < n; i++) { sink = new Order(); }
        long ns = System.nanoTime() - t0;
        long gcAfter = collections();

        System.out.printf("%,d Order objects in %,d ms%n", n, ns / 1_000_000);
        System.out.printf("about %.1f ns each - that is a bump of the TLAB pointer%n", (double) ns / n);
        System.out.printf("24 bytes each, so roughly %,d MB of garbage was produced%n", (24L * n) >> 20);
        System.out.println("collections that ran while it happened: " + (gcAfter - gcBefore));
        System.out.println();
        System.out.println("Allocation itself is nearly free: no lock, no free list, no malloc.");
        System.out.println("The cost is the collection it eventually causes - and almost every");
        System.out.println("one of these objects was already dead when that collection ran.");
    }

    static long collections() {
        long total = 0;
        for (GarbageCollectorMXBean gc : ManagementFactory.getGarbageCollectorMXBeans()) {
            long c = gc.getCollectionCount();
            if (c > 0) { total += c; }
        }
        return total;
    }
}

Allocation is a pointer bump. The bill arrives at the next young GC.

This is a reading copy. The full lesson — with the visual explainer, the interactive lab and a Run button for the code — lives in the JVM course, and every lesson in it is listed on the JVM contents page.