Skip to content

AxonOps — AI-Native Control Plane for Open Source Data Platforms

Cassandra and Java Virtual Machine (JVM)

Cassandra is written in Java and runs on the Java Virtual Machine (JVM). The JVM provides automatic memory management through garbage collection (GC), which periodically reclaims memory from unused objects. Understanding JVM behavior is essential for operating Cassandra effectively.


Garbage collection has been both a strength and a challenge for Java applications like Cassandra. Understanding this history explains why GC tuning has historically been critical for Cassandra operations—and why modern GCs are changing this.

All traditional garbage collectors require stop-the-world (STW) pauses—moments when all application threads freeze while the GC performs certain operations. For databases, these pauses directly impact:

  • Query latency (p99 spikes during GC)
  • Coordinator timeouts (nodes appearing unresponsive)
  • Cluster stability (gossip failures during long pauses)

CMS: The First Low-Pause Collector (JDK 1.4, 2002)

Section titled “CMS: The First Low-Pause Collector (JDK 1.4, 2002)”

The Concurrent Mark-Sweep (CMS) collector was Java's first attempt at reducing pause times. CMS performed most of its work concurrently with application threads, but still required STW pauses for initial marking and final remarking.

CMS was the recommended collector for Cassandra for many years, but had significant drawbacks:

  • Fragmentation: CMS did not compact memory, leading to fragmentation over time
  • Concurrent mode failure: If allocation outpaced collection, a full STW collection occurred
  • Tuning complexity: Required careful tuning of dozens of parameters
  • Deprecated: Removed in JDK 14
Terminal window
# Historical CMS configuration (do not use)
# -XX:+UseConcMarkSweepGC
# -XX:+CMSParallelRemarkEnabled
# -XX:CMSInitiatingOccupancyFraction=75

CMS is Deprecated

CMS was deprecated in JDK 9 and removed in JDK 14. Do not use CMS for new deployments.

G1 (Garbage First) replaced CMS as the default collector in JDK 9. G1 divides the heap into regions and prioritizes collecting regions with the most garbage, aiming to meet a configurable pause time target.

G1 improvements over CMS:

  • Compaction: Eliminates fragmentation
  • Predictable pauses: Targets a maximum pause time (-XX:MaxGCPauseMillis)
  • Self-tuning: Requires less manual configuration
  • No concurrent mode failure: Degrades gracefully under pressure

However, G1 still has STW pauses that scale with heap size and object graph complexity—typically 50-500ms for Cassandra workloads.

Modern Era: Sub-Millisecond Pauses (2017+)

Section titled “Modern Era: Sub-Millisecond Pauses (2017+)”

The latest generation of garbage collectors achieves pause times in the low single-digit milliseconds regardless of heap size, effectively eliminating GC as an operational concern.

Shenandoah (Red Hat, 2017)

  • Concurrent compaction eliminates long STW pauses
  • Pause times typically 1-5ms
  • Backported to JDK 8 and 11 in OpenJDK distributions
  • Production-proven with Cassandra workloads

ZGC (Oracle, 2018)

  • Sub-millisecond pauses (< 1ms target)
  • Designed for very large heaps (multi-terabyte)
  • Production-ready in JDK 15+

Generational garbage collection separates objects by age:

  • Young generation: Newly allocated objects (most die quickly)
  • Old generation: Long-lived objects

This allows the collector to focus on the young generation where most garbage is created, improving overall efficiency.

Generational ZGC (JDK 21+, production-ready)

Terminal window
# JDK 21+ Generational ZGC
-XX:+UseZGC
-XX:+ZGenerational

Generational Shenandoah (JDK 25+, production-ready)

Generational Shenandoah was experimental in JDK 21-24 and became a production feature in JDK 25 (September 2025).

Terminal window
# JDK 25+ Generational Shenandoah
-XX:+UseShenandoahGC
-XX:ShenandoahGCMode=generational

Cassandra Does Not Yet Support JDK 21+

Cassandra does not currently support JDK 21 or later. JDK 21 support is tracked in CASSANDRA-18831. Until JDK 21+ support is added, the generational GC options above are not available for Cassandra deployments.

YearCollectorTypical PauseKey Innovation
2002CMS100-500msConcurrent marking
2011G150-500msRegion-based, predictable pauses
2017Shenandoah1-5msConcurrent compaction
2018ZGC< 1msColored pointers, load barriers
2023Generational ZGC (JDK 21)< 1msGenerational + concurrent compaction
2025Generational Shenandoah (JDK 25)< 1msGenerational + concurrent compaction

Note: Timeline shows JDK release years. Cassandra does not yet support JDK 21+, so Generational ZGC and Generational Shenandoah are not currently available for Cassandra.


Cassandra VersionSupported JDKRecommended
4.0JDK 8, 11JDK 11
4.1JDK 8, 11JDK 11
5.0JDK 11, 17JDK 17

Use Latest Patch Version

Always use the latest patch version of the chosen JDK for security updates and bug fixes.


Several JDK distributions are available, all based on OpenJDK. The choice depends on support requirements, licensing, and specific features like garbage collectors.

DistributionVendorLicenseShenandoahNotes
OpenJDKOracle/CommunityGPL v2JDK 12+Reference implementation
Eclipse TemurinAdoptiumGPL v2JDK 17+Formerly AdoptOpenJDK; widely used
Amazon CorrettoAmazonGPL v2Verify per versionLong-term support; production-tested at Amazon
Azul ZuluAzulGPL v2Verify per versionFree community and paid enterprise editions
Red Hat OpenJDKRed HatGPL v2JDK 8+Shenandoah backported to JDK 8
Oracle JDKOracleCommercialNoRequires license for production use
GraalVMOracleGPL v2 / CommercialNoNot recommended for Cassandra

For Cassandra production deployments:

Eclipse Temurin (Adoptium)

Amazon Corretto

Azul Zulu

Shenandoah Availability

Shenandoah GC is not included in all JDK distributions. If low-latency GC is required, verify Shenandoah support before selecting a distribution. Amazon Corretto, Azul Zulu, and Red Hat OpenJDK all include Shenandoah in their JDK 11 builds.


The garbage collector (GC) automatically frees memory by identifying and removing objects that are no longer referenced. During GC cycles, application threads pause briefly—these GC pauses directly impact query latency.

Heap SizeGC FrequencyGC Pause DurationImpact
Small (4-8GB)More frequentShorter pauses (workload-dependent)Lower p99 latency
Medium (16-24GB)ModerateModerate pauses (workload-dependent)Balanced
Large (>24GB)Less frequentLonger pauses (workload-dependent)Higher p99 latency spikes

Note: Actual pause durations vary significantly based on workload, object allocation patterns, and GC configuration.

Cassandra uses the G1 (Garbage First) garbage collector by default. G1 divides the heap into regions and collects the regions with the most garbage first, aiming to limit pause times while maintaining throughput.

G1 Configuration:

Terminal window
# jvm-server.options
# Enable G1 (default in modern JDKs)
-XX:+UseG1GC
# Target maximum GC pause time
-XX:MaxGCPauseMillis=500
# G1 region size (auto-calculated if not set)
# -XX:G1HeapRegionSize=16m

ZGC is a low-latency garbage collector with pause times typically under 1ms regardless of heap size. It is available in JDK 15+ (production-ready) and experimentally in JDK 11-14.

Terminal window
# jvm-server.options
# Enable ZGC (JDK 15+)
-XX:+UseZGC
# ZGC works well with larger heaps
-Xms32G
-Xmx32G

Limited Production Evidence

While ZGC performs well in benchmarks, there is limited production evidence specific to Cassandra workloads compared to G1 and Shenandoah. Thorough testing in non-production environments is recommended before deployment.

Shenandoah is a low-pause garbage collector developed by Red Hat that performs concurrent compaction, reducing GC pause times to low single-digit milliseconds regardless of heap size. This effectively eliminates the GC pause issues that historically plagued large-heap Java applications.

Shenandoah is available in:

  • OpenJDK 12+ (standard)
  • OpenJDK 11 (backport in most distributions)
  • OpenJDK 8 (backport in Red Hat and Amazon Corretto builds)
Terminal window
# jvm-server.options
# Enable Shenandoah
-XX:+UseShenandoahGC
# Optional: tune for ultra-low pause (may reduce throughput)
-XX:ShenandoahGCHeuristics=compact

Shenandoah for Cassandra

Shenandoah is particularly well-suited for Cassandra workloads because:

  • Predictable latency: Pause times typically 1-5ms, eliminating p99 latency spikes from GC
  • Large heap friendly: No penalty for heaps >31GB (though CompressedOops limit still applies)
  • Write-heavy workloads: Concurrent collection handles high allocation rates well
  • Wide availability: Backported to JDK 11 and 8, unlike ZGC
CollectorTypical PauseThroughputCPU OverheadProduction Evidence
G150-500msHighLowExtensive
Shenandoah1-5msMedium-HighMediumGood (OpenJDK users)
ZGC< 1msMedium-HighMediumLimited for Cassandra

When to use each:

  • G1: Default choice, well-proven, good balance of throughput and latency
  • Shenandoah: Recommended for low latency needs, proven in production, available on JDK 11+
  • ZGC: Experimental for Cassandra; test thoroughly before production use

Low-Latency GC Trade-offs

Both ZGC and Shenandoah achieve low pause times by performing more work concurrently with application threads. This requires:

  • ~10-15% more CPU for GC work
  • ~3-5% more memory overhead
  • Slightly lower peak throughput than G1

For most Cassandra deployments, the latency improvements far outweigh these costs.


Server RAMRecommended HeapRationale
16GB4-8GBSmall deployments
32GB8-16GBStandard production
64GB16-24GBLarge deployments
128GB+24-31GBMaximum practical heap

31GB Limit

Do not exceed 31GB heap. Beyond this threshold, the JVM cannot use compressed ordinary object pointers (CompressedOops), effectively wasting ~4GB of addressable memory. A 31GB heap often outperforms a 48GB heap.

Terminal window
# jvm-server.options
# Set heap size (min = max for predictable behavior)
-Xms24G
-Xmx24G

Setting -Xms equal to -Xmx prevents heap resizing during operation, which can cause latency spikes.

The young generation holds newly created objects. For Cassandra workloads:

Terminal window
# jvm-server.options
# Young generation size (G1 typically auto-tunes this well)
# Only set if experiencing issues
# -Xmn8G
# G1: target young gen size as percentage of heap
# -XX:G1NewSizePercent=20
# -XX:G1MaxNewSizePercent=30

Cassandra uses separate JVM options files for different environments:

FilePurpose
jvm-server.optionsProduction server settings
jvm11-server.optionsJDK 11-specific options
jvm17-server.optionsJDK 17-specific options
jvm-clients.optionsClient tools (nodetool, cqlsh)

Location: $CASSANDRA_HOME/conf/ or /etc/cassandra/

Terminal window
# jvm-server.options
# Heap settings
-Xms24G
-Xmx24G
# GC settings
-XX:+UseG1GC
-XX:MaxGCPauseMillis=500
# GC logging (JDK 11+)
-Xlog:gc*:file=/var/log/cassandra/gc.log:time,uptime,level,tags:filecount=10,filesize=100M
# Out of memory handling
-XX:+ExitOnOutOfMemoryError
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/var/lib/cassandra/java_pid.hprof
# Performance
-XX:+AlwaysPreTouch
-XX:-UseBiasedLocking

Terminal window
# jvm-server.options (JDK 11+)
-Xlog:gc*:file=/var/log/cassandra/gc.log:time,uptime,level,tags:filecount=10,filesize=100M
Terminal window
# Check GC pause times
grep "pause" /var/log/cassandra/gc.log
# Using Cassandra's nodetool
nodetool gcstats
MetricHealthy RangeAction if Exceeded
GC pause time< 500msReduce heap or tune GC
GC frequency< 1/minute for full GCCheck for memory leaks
Heap after GC< 70% of maxIncrease heap if growing
Allocation rateStableInvestigate if spiking

Symptoms:

  • Query timeouts
  • High p99 latency
  • "GC pause" warnings in logs

Solutions:

  1. Switch to a low-latency GC (Shenandoah or ZGC)
  2. Reduce heap size (counterintuitive but often effective with G1)
  3. Move memtables off-heap (see Memory Management)
  4. Tune G1 settings:
    Terminal window
    -XX:MaxGCPauseMillis=300
    -XX:G1HeapRegionSize=16m

Recommended: Shenandoah

If experiencing GC pause issues, switching to Shenandoah often provides immediate relief without extensive tuning. Shenandoah is available on OpenJDK 11+ and has proven effective for Cassandra workloads.

Symptoms:

  • Node crashes with OOM
  • "java.lang.OutOfMemoryError" in logs

Solutions:

  1. Increase heap (up to 31GB)
  2. Reduce number of tables
  3. Move data structures off-heap
  4. Check for memory leaks with heap dump analysis
Terminal window
# Analyze heap dump
jmap -histo:live <pid> | head -20

Symptoms:

  • High system CPU usage
  • Frequent GC cycles
  • Low throughput

Solutions:

  1. Increase heap to reduce GC frequency
  2. Check for excessive object allocation (profiling)
  3. Tune young generation size

Terminal window
# jvm-server.options
# Heap
-Xms24G
-Xmx24G
# G1 GC
-XX:+UseG1GC
-XX:MaxGCPauseMillis=500
-XX:+ParallelRefProcEnabled
# Performance
-XX:+AlwaysPreTouch
-XX:-UseBiasedLocking
-XX:+UseStringDeduplication
# Crash handling
-XX:+ExitOnOutOfMemoryError
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/var/lib/cassandra/
# GC logging
-Xlog:gc*:file=/var/log/cassandra/gc.log:time,uptime,level,tags:filecount=10,filesize=100M
  • Set heap > 31GB
  • Use CMS garbage collector (deprecated)
  • Disable compressed oops manually
  • Run without GC logging in production