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
EnumSetfits entirely inside a single 8-bytelongprimitive field. - Single CPU Cycle Execution: Operations like
contains()execute in 1 CPU clock cycle ((vector & (1L << ordinal)) != 0). - Array Direct Indexing:
EnumMapreplaces hash bucket arrays with a simple flat array indexed directly byenum.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
| Metric | HashSet<Role> | EnumSet<Role> |
|---|---|---|
| Backing Storage | HashMap bucket table + nodes | 64-bit long primitive |
| Lookup Mechanics | Hash calculation + bucket list traversal | Single CPU bitwise AND (&) |
| Memory Per Set | ~128+ bytes heap allocation | 8 bytes primitive bitmask |
| Iteration Speed | Scans array buckets | Bit-count instruction (Long.numberOfTrailingZeros) |
Quick Summary
EnumSetrepresents element membership using bit positions inside a 64-bitlongbitmask.- Set operations (
add,contains,remove,union) compile to single CPU bitwise operations. EnumMapindexes values directly usingkey.ordinal(), completely eliminating hash code calculations and collisions.
References & Further Reading
- Scherer, D., Lea, D., & Scott, M. L. (2006). Scalable Synchronous Queues. Proceedings of ACM PPoPP ‘06, 147–156.
- OpenJDK Repository. OpenJDK 21 Source Code:
java.util.concurrent.SynchronousQueue. GitHub. - Goetz, B., et al. (2006). Java Concurrency in Practice. Addison-Wesley.
Part 20: Java WeakHashMap Internals: Preventing Memory Leaks with Weak References
Continue to Part 20 →