Adetayo Akinsanya unkletayo.dev
Engineering / Java Collections From Scratch • Part 19 of 26 Published

High-Performance Java: How EnumSet and EnumMap Achieve Zero-Allocation Speed

64-bit long bitmask vectors, single-cycle CPU bitwise logic, and ordinal array indexing.

Part 19 in Series — Catch up on the previous article: Java ConcurrentHashMap Internals: Lock-Free CAS & Fine-Grained Bucket Sync (Part 18) before diving into this post.

Suppose you are building a microservice authorization engine evaluating user permissions for a high-frequency trading API.

Every request carries a set of permission flags (READ, WRITE, EXECUTE, ADMIN, AUDIT). With 50,000 incoming requests per second, your authorization check executes 50,000 times per second.

If you store active permissions inside a standard HashSet<PermissionEnum>, every request instantiates iterator objects, calculates hash codes, and follows bucket pointers. Garbage collection pauses spike, adding 10 milliseconds of latency to trading execution.

Java provides a specialized set implementation designed specifically for enums: EnumSet.

EnumSet does not use hash tables, nodes, or object pointers. It represents the entire set as a single 64-bit long primitive variable. Adding, removing, or checking an element compiles down to a single raw CPU bitwise instruction (AND, OR, NOT).


Why You Need This in Real Life

When working with bounded sets of known constants (Java enum types), EnumSet and EnumMap outperform standard collections by orders of magnitude:

  • Zero Allocation Memory Footprint: A small EnumSet fits entirely inside a single 8-byte long primitive field.
  • Single CPU Cycle Execution: Operations like contains() execute in 1 CPU clock cycle ((vector & (1L << ordinal)) != 0).
  • Array Direct Indexing: EnumMap replaces hash bucket arrays with a simple flat array indexed directly by enum.ordinal(). Zero hash collisions, zero rehashing.

How EnumSet Packs Elements into Bits

Java enums assign an immutable integer position (ordinal()) to each constant:

public enum Role {
    READ,    // ordinal 0
    WRITE,   // ordinal 1
    EXECUTE, // ordinal 2
    ADMIN,   // ordinal 3
    AUDIT    // ordinal 4
}

Instead of storing object reference pointers inside heap buckets, RegularEnumSet represents element presence as bit flags inside an 8-byte long elements bitmask:

Bit Position:  63 ... 4  3  2  1  0
Bit Vector:   [ 0 ... 0  0  1  1  1 ]
                         |  |  |  +---> READ present (1L << 0)
                         |  |  +------> WRITE present (1L << 1)
                         |  +---------> EXECUTE present (1L << 2)

Bitwise Set Operations in Hardware

Because elements correspond to bit positions, mathematical set operations execute directly via CPU logic gates:

// Check if set contains WRITE (ordinal 1)
boolean hasWrite = (elements & (1L << Role.WRITE.ordinal())) != 0;

// Add EXECUTE (ordinal 2)
elements |= (1L << Role.EXECUTE.ordinal());

// Remove WRITE (ordinal 1)
elements &= ~(1L << Role.WRITE.ordinal());

// Set Intersection (Set A AND Set B)
long intersection = elementsA & elementsB;

// Set Union (Set A OR Set B)
long union = elementsA | elementsB;

If an enum type contains more than 64 constants, Java automatically switches implementation from RegularEnumSet (single long) to JumboEnumSet (long[] array of bit words).


Building MyEnumMap<K extends Enum<K>, V>

Standard HashMap calculates key hash codes and manages bucket collision chains.

Because enum constants have fixed, bounded ordinals, EnumMap uses a simple flat Object[] array where the array index is key.ordinal():

import java.util.Arrays;

public class MyEnumMap<K extends Enum<K>, V> {
    private final Class<K> keyType;
    private final K[] keyUniverse;
    private Object[] values;
    private int size = 0;

    public MyEnumMap(Class<K> keyType) {
        this.keyType = keyType;
        this.keyUniverse = keyType.getEnumConstants();
        this.values = new Object[keyUniverse.length];
    }

    public V put(K key, V value) {
        checkKey(key);
        int index = key.ordinal();
        V oldValue = (V) values[index];
        if (oldValue == null) {
            size++;
        }
        values[index] = value;
        return oldValue;
    }

    @SuppressWarnings("unchecked")
    public V get(K key) {
        if (key == null) return null;
        return (V) values[key.ordinal()];
    }

    public boolean containsKey(K key) {
        return key != null && values[key.ordinal()] != null;
    }

    public int size() {
        return size;
    }

    private void checkKey(K key) {
        if (key == null) {
            throw new NullPointerException("Enum key cannot be null");
        }
    }
}

Performance Comparison: HashSet vs EnumSet

MetricHashSet<Role>EnumSet<Role>
Backing StorageHashMap bucket table + nodes64-bit long primitive
Lookup MechanicsHash calculation + bucket list traversalSingle CPU bitwise AND (&)
Memory Per Set~128+ bytes heap allocation8 bytes primitive bitmask
Iteration SpeedScans array bucketsBit-count instruction (Long.numberOfTrailingZeros)

Quick Summary

  • EnumSet represents element membership using bit positions inside a 64-bit long bitmask.
  • Set operations (add, contains, remove, union) compile to single CPU bitwise operations.
  • EnumMap indexes values directly using key.ordinal(), completely eliminating hash code calculations and collisions.

References & Further Reading

  1. Scherer, D., Lea, D., & Scott, M. L. (2006). Scalable Synchronous Queues. Proceedings of ACM PPoPP ‘06, 147–156.
  2. OpenJDK Repository. OpenJDK 21 Source Code: java.util.concurrent.SynchronousQueue. GitHub.
  3. Goetz, B., et al. (2006). Java Concurrency in Practice. Addison-Wesley.

Up Next in Series →

Part 20: Java WeakHashMap Internals: Preventing Memory Leaks with Weak References

Continue to Part 20 →