Definition
- Epsilon GC – a no-op (passive) garbage collector introduced in Java 11 via JEP 318 that handles allocation but never reclaims memory.
- No-op collection – no marking, no relocation, no compaction; the only memory operation is bump-pointer allocation.
- OutOfMemoryError – what happens when the heap fills under Epsilon, since nothing can be freed.
- Benchmarking use – removing all GC noise to measure the raw cost of code and allocation.
- Allocation-rate analysis – running a known workload under Epsilon with a fixed heap to measure bytes allocated per unit of work.
Epsilon is like a bank that gives out desks to every new customer but never clears any desk. As long as the floor has free desks everything is fast and there is zero cleaning-crew interruption. The moment the floor is full, the bank just closes the doors. You would only run the bank this way to measure exactly how fast desks fill up, or for a pop-up event that you know will end before the floor is full.
Understanding the Concepts
What is Epsilon GC?
Epsilon implements the full GC interface the JVM expects, but its collection logic is empty. It allocates objects by bumping a pointer through the heap and tracks nothing for reclamation. There are no GC threads, no barriers, no pauses.
# Enable Epsilon GC (experimental, requires unlock)
java -XX:+UnlockExperimentalVMOptions \
-XX:+UseEpsilonGC \
-Xmx256m \
-jar interest-calc.jar
Epsilon behavior: Allocation: bump pointer (fastest possible) Collection: none Pauses: zero When heap is full: OutOfMemoryError → JVM stops
How do you use Epsilon for benchmarking?
Epsilon removes GC from the picture entirely, so microbenchmark numbers reflect only allocation and your code logic.
package com.bank.benchmark;
import java.math.BigDecimal;
import java.math.MathContext;
public class InterestBenchmark {
public static void main(String[] args) {
long start = System.nanoTime();
BigDecimal total = BigDecimal.ZERO;
// Calculate compound interest for 1 million accounts
for (int i = 0; i < 1_000_000; i++) {
BigDecimal principal = BigDecimal.valueOf(100_000 + i);
BigDecimal rate = BigDecimal.valueOf(0.065);
BigDecimal interest = principal.multiply(rate,
MathContext.DECIMAL64);
total = total.add(interest);
}
long elapsed = System.nanoTime() - start;
System.out.printf("Total interest: %s%n", total);
System.out.printf("Time: %d ms%n", elapsed / 1_000_000);
}
}
# Benchmark with Epsilon (no GC noise)
java -XX:+UnlockExperimentalVMOptions \
-XX:+UseEpsilonGC \
-Xmx512m \
-cp . com.bank.benchmark.InterestBenchmark
# Compare with G1 (includes GC noise)
java -XX:+UseG1GC \
-Xmx512m \
-cp . com.bank.benchmark.InterestBenchmark
Epsilon: Time: 234 ms (pure computation + allocation)
G1: Time: 267 ms (includes 33 ms of GC pauses)
How do you measure allocation rate with Epsilon?
Run a known workload with a fixed heap and see how fast it fills. This gives you a precise bytes-per-operation figure.
# Measure allocation rate
java -XX:+UnlockExperimentalVMOptions \
-XX:+UseEpsilonGC \
-Xmx1g \
-XX:+HeapDumpOnOutOfMemoryError \
-XX:HeapDumpPath=/tmp/epsilon-heap.hprof \
-Xlog:gc*:file=epsilon.log \
-jar interest-calc.jar
epsilon.log:
[0.001s] Heap: 1024M reserved, 0M committed
[0.500s] Heap: 1024M reserved, 512M committed (filling...)
[0.987s] Heap: 1024M reserved, 1024M committed
[0.988s] OutOfMemoryError: Java heap space
Calculation:
1 GB filled in 0.987 seconds
Allocation rate: ~1.01 GB/sec
If workload processed 500,000 accounts:
Allocation per account: 1 GB / 500,000 = ~2 KB/account
When is Epsilon useful for short-lived programs?
A small tool, a one-shot batch job, or a serverless function that finishes before the heap fills gets maximum throughput with zero pauses.
package com.bank.tool;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.stream.Stream;
public class AccountValidator {
// Short-lived tool: validate account file and exit
public static void main(String[] args) throws IOException {
Path file = Path.of(args[0]);
long invalidCount;
try (Stream<String> lines = Files.lines(file)) {
invalidCount = lines
.filter(line -> !isValidAccount(line))
.count();
}
System.out.println("Invalid accounts: " + invalidCount);
// Program exits -- heap is reclaimed by OS
}
private static boolean isValidAccount(String line) {
return line != null && line.length() >= 10;
}
}
# Short-lived tool with Epsilon: fastest possible, zero GC overhead
java -XX:+UnlockExperimentalVMOptions \
-XX:+UseEpsilonGC \
-Xmx128m \
-cp . com.bank.tool.AccountValidator accounts.csv
How does Epsilon expose memory leaks?
Running under Epsilon makes leaks obvious: anything that a real collector would normally reclaim now accumulates until the JVM dies, so a leak shows up immediately.
package com.bank.service;
import java.util.HashMap;
import java.util.Map;
public class LeakyCache {
// Under Epsilon, this fills the heap fast because nothing is freed
private static final Map<String, byte[]> cache = new HashMap<>();
public static void main(String[] args) {
int i = 0;
while (true) {
cache.put("key-" + i, new byte[10_000]);
i++;
}
// Under G1: runs forever (GC reclaims... oh wait, it can't
// because the map holds everything -- still a leak, but
// slower to notice)
// Under Epsilon: OutOfMemoryError in seconds -- leak obvious!
}
}
# Epsilon makes the leak fail fast
java -XX:+UnlockExperimentalVMOptions \
-XX:+UseEpsilonGC \
-Xmx256m \
-XX:+HeapDumpOnOutOfMemoryError \
-cp . com.bank.service.LeakyCache
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
Dumping heap to /tmp/java_pid12345.hprof
# Heap dump reveals the HashMap holding everything
How is Epsilon different from other collectors?
| Aspect | Epsilon | Serial | G1 | ZGC |
|---|---|---|---|---|
| Collects garbage? | No | Yes | Yes | Yes |
| Pauses | Zero | Full STW | Bounded STW | Sub-ms |
| Allocation speed | Fastest (bump pointer) | Fast | Fast | Fast + barrier |
| Long-running? | No (heap fills) | Yes | Yes | Yes |
| Use case | Benchmark, analysis | Tiny heaps | General | Low-latency |
What are common mistakes?
The most common mistake is accidentally leaving Epsilon enabled in a production service that runs indefinitely.
# WRONG: Epsilon in production -- service crashes when heap fills
java -XX:+UnlockExperimentalVMOptions \
-XX:+UseEpsilonGC \
-jar transfer-service.jar
# CRASH: OutOfMemoryError after minutes/hours
# CORRECT: Epsilon only for measurement, G1/ZGC for production
java -XX:+UseG1GC -jar transfer-service.jar
What should a senior engineer know?
A senior engineer treats Epsilon as a precise laboratory instrument. At National Bank of India, Epsilon was used to measure the interest-calculation routine: running it under a fixed heap showed exactly how many bytes the routine allocated per account, revealing far more intermediate objects than necessary. The team optimized the routine and then ran production under G1.
# Workflow: Epsilon for measurement, real collector for production
# 1. Measure allocation rate with Epsilon
java -XX:+UnlockExperimentalVMOptions -XX:+UseEpsilonGC \
-Xmx1g -Xlog:gc* -jar interest-calc.jar
# 2. Optimize based on measurement
# 3. Run production with appropriate collector
java -XX:+UseG1GC -XX:MaxGCPauseMillis=100 \
-Xmx4g -jar interest-calc.jar
Visual Overview
Epsilon GC lifecycle:
─────────────────────────────────────────
Start: heap is empty
│
│ allocate (bump pointer, fastest possible)
▼
┌───────────────────────────────┐
│ [obj][obj][obj][obj][obj]... │
│ Heap fills from left to right │
│ Nothing is ever freed │
└───────────────────────────────┘
│
│ heap full
▼
OutOfMemoryError → JVM stops
Use cases:
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ Benchmarking │ │ Alloc rate │ │ Short-lived │
│ (zero GC noise) │ │ measurement │ │ programs │
└─────────────────┘ └─────────────────┘ └─────────────────┘
Key Takeaway
Epsilon is the no-op garbage collector: it allocates by bumping a pointer and never reclaims anything. There are no GC threads, no barriers, and zero pauses. When the heap fills, the JVM throws OutOfMemoryError. It is an experimental collector enabled behind an unlock flag, meant for measurement, not production.
The three legitimate uses are benchmarking (removing all GC noise from measurements), allocation-rate analysis (measuring bytes per operation by watching how fast a fixed heap fills), and short-lived programs that finish before the heap would fill. It also exposes memory leaks fast because nothing is ever freed.
At National Bank of India, Epsilon measured the interest-calculation routine's allocation rate, revealing unnecessary intermediate objects. The team optimized and then ran production under G1.
Remember one sentence: Epsilon allocates but never collects, which makes it the perfect laboratory instrument for measuring allocation, but never the right choice for a service that must run longer than its heap can hold.
---