One of the FX trading desks kept a reference data structure in memory: all live position limits and risk parameters for every currency pair, updated in real time from a risk management system. The structure held about 40GB of data and was read by the trading engine on every price update.
Putting 40GB on the Java heap was not an option. GC pauses on a 40GB heap are seconds, not milliseconds. The solution was off-heap allocation — memory that exists outside the GC’s visibility, managed explicitly by the application.
What “Off-Heap” Means
The JVM manages two kinds of memory:
On-heap: objects created with new. Managed by the garbage collector. Automatic allocation and reclamation. Visible to GC.
Off-heap: memory allocated outside the GC heap. Not tracked by the garbage collector. Must be explicitly freed (or managed via try-with-resources). Invisible to GC — no impact on GC pause time, regardless of size.
JVM Process Memory
┌─────────────────────────────────────────────┐
│ Java Heap (GC-managed) │
│ -Xmx controls size │
│ Everything in here → GC sees it │
├─────────────────────────────────────────────┤
│ Off-heap / Native Memory │
│ No JVM size limit (OS limit only) │
│ GC doesn't see it → no pause impact │
│ Must be freed explicitly │
└─────────────────────────────────────────────┘
sun.misc.Unsafe: The Low-Level API
sun.misc.Unsafe (renamed to jdk.internal.misc.Unsafe in Java 9, but still accessible) exposes raw memory operations:
| |
This is essentially malloc/free from C, accessible from Java. There are no bounds checks, no null checks, no GC. Write past the end of your allocation and you corrupt memory. Forget to call freeMemory and you leak. Unsafe is aptly named.
A Simple Off-Heap Structure
A fixed-size record store for currency pair limits — 8 bytes per field, 6 fields, packed:
| |
For a 5,000-pair structure, this allocates 240KB — tiny. The 40GB case is the same pattern but with more complex fields and a larger capacity.
Reading this structure in a tight loop is significantly faster than reading equivalent on-heap objects because:
- No object headers (a Java object has a 16-byte header before the first field)
- Records are packed contiguously — sequential access is cache-friendly
- No GC interference, even under heavy allocation pressure
Chronicle Map: Off-Heap Hash Map
Writing raw Unsafe code for production use is risky. Chronicle Map wraps off-heap allocation in a safe, usable API — an off-heap Map<K, V> with persistence to a memory-mapped file:
| |
Chronicle Map stores keys and values in off-heap memory. The LimitData value is serialised to a contiguous byte region — no GC overhead for reads or writes.
The try-with-value pattern for reads is important: it’s a zero-copy path where data is a flyweight pointing directly into the off-heap memory, not a copied object. The lock on the map entry is held until the try block exits.
Memory-Mapped Files for Persistence
Both Chronicle Map and Chronicle Queue use memory-mapped files for persistence. The OS maps a file on disk into the process’s virtual address space. Writes to the mapped region eventually flush to the file; reads from the region may or may not hit disk depending on the OS page cache.
| |
For the risk parameter store, we used a memory-mapped file on a tmpfs (RAM-backed filesystem). This gave us:
- Persistence (the file exists between process restarts)
- Off-heap storage (not visible to GC)
mmapsemantics (OS manages the virtual memory mapping)- RAM performance (no actual disk I/O)
On process restart, the data was immediately available from the file without re-loading from the source system.
The Caution You Must Internalize
Off-heap code is memory-unsafe code. The JVM cannot protect you from:
| |
The discipline required:
- Every
allocateMemorymust have a correspondingfreeMemorypath - Bounds checking in any code that takes external indices
- Thread safety —
putLongis not atomic unless you useputLongVolatile - Valgrind or similar tools for testing memory correctness
For most applications, the GC overhead of on-heap storage is acceptable, and the safety is worth it. Off-heap is the right choice specifically when the data structure is large enough that GC visibility would cause unacceptable pause times, and when you have the engineering discipline to manage it correctly.