---
title: "ActiveMQ JVM Memory & GC Tuning: Heap Sizing, G1GC, ZGC Guide"
date: 2026-07-06
author: "TheFrameGuy"
featured_image: "https://www.meshiq.com/wp-content/uploads/blog_activeMQ-JVM-memory_07062026.jpg"
categories:
  - name: "Apache ActiveMQ®"
    url: "/sort-by/active-mq.md"
  - name: "Messaging"
    url: "/sort-by/messaging.md"
  - name: "Middleware"
    url: "/sort-by/middleware.md"
  - name: "Middleware Optimization"
    url: "/sort-by/middleware-optimization.md"
  - name: "Monitoring"
    url: "/sort-by/monitoring.md"
  - name: "MQ"
    url: "/sort-by/mq.md"
tags:
  - name: "middleware"
    url: "/sort-by/tag/middleware.md"
  - name: "monitoring"
    url: "/sort-by/tag/monitoring.md"
  - name: "Observability"
    url: "/sort-by/tag/observability.md"
---

# ActiveMQ JVM Memory & GC Tuning: Heap Sizing, G1GC, ZGC Guide

This guide covers ActiveMQ JVM memory management and GC tuning from the ground up: where to set JVM flags for Apache ActiveMQ® and Apache Artemis™, the JVM heap memory sizing formula, the fixed-heap rule, GC algorithms selection (G1GC, ZGC, Shenandoah), how GC pauses affect message delivery, the three distinct OOM error types and their specific fixes, KahaDB index cache heap pressure, GC logging configuration, and the container memory alignment required for Kubernetes deployments.

## Where to Configure JVM Options

### ActiveMQ Apache ActiveMQ®: bin/env

JVM options for Apache ActiveMQ® are set in ACTIVEMQ\_OPTS, defined in bin/env (Linux/macOS) or bin/activemq.bat (Windows). This is the correct location, editing the main bin/activemq launch script directly loses changes on upgrade.

bash  
\# bin/env — ActiveMQ Classic JVM memory configuration  
\# Edit this file; do NOT edit bin/activemq directly.  
  
\# Memory: fixed heap (Xms = Xmx — the most important rule)  
ACTIVEMQ\_OPTS\_MEMORY=”-Xms2g -Xmx2g”  
  
ACTIVEMQ\_OPTS=”$ACTIVEMQ\_OPTS\_MEMORY \\  
 -XX:+UseG1GC \\  
 -XX:MaxGCPauseMillis=20 \\  
 -XX:InitiatingHeapOccupancyPercent=75 \\  
 -XX:NewRatio=4 \\  
 -XX:+UseStringDeduplication \\  
 -XX:+AlwaysPreTouch \\  
 -XX:MaxMetaspaceSize=512m \\  
 -Dorg.apache.activemq.UseDedicatedTaskRunner=false \\  
 -XX:+UseContainerSupport \\  
 -Xlog:gc\*:file=${ACTIVEMQ\_DATA}/gc.log:time,uptime,level,tags:filecount=5,filesize=20m \\  
 -XX:+HeapDumpOnOutOfMemoryError \\  
 -XX:HeapDumpPath=${ACTIVEMQ\_DATA}/heap-dump.hprof \\  
 -XX:OnOutOfMemoryError=’kill -9 %p'”  
export ACTIVEMQ\_OPTS

### ActiveMQ Apache Artemis™: etc/artemis.profile

For Apache Artemis™, JVM options live in the instance’s etc/artemis.profile:

bash  
\# ARTEMIS\_INSTANCE/etc/artemis.profile  
JAVA\_ARGS=”-Xms2g -Xmx2g \\  
 -XX:+UseG1GC \\  
 -XX:MaxGCPauseMillis=20 \\  
 -XX:InitiatingHeapOccupancyPercent=75 \\  
 -XX:NewRatio=4 \\  
 -XX:+UseStringDeduplication \\  
 -XX:+AlwaysPreTouch \\  
 -XX:MaxMetaspaceSize=512m \\  
 -XX:+UseContainerSupport \\  
 -Xlog:gc\*:file=${ARTEMIS\_INSTANCE}/log/gc.log:time,uptime,level,tags:filecount=5,filesize=20m \\  
 -XX:+HeapDumpOnOutOfMemoryError \\  
 -XX:HeapDumpPath=${ARTEMIS\_INSTANCE}/log/heap-dump.hprof \\  
 -XX:OnOutOfMemoryError=’kill -9 %p'”

## JVM Heap Memory Sizing: The Formula, the Fixed-Heap Rule, and Kubernetes Alignment

### The JVM Heap Memory Sizing Formula

JVM heap memory (-Xmx) = 50–60% of available physical RAM

The JVM requires substantially more memory than -Xmx alone accounts for. Off-heap components such as: Metaspace (class metadata), per-thread stack allocations, JIT code cache, JVM internal structures, add 20–30% beyond the heap.

On a dedicated 8GB broker host, 4–5GB is the appropriate JVM heap memory ceiling. The remaining RAM is used by the OS for file system page caching (which improves KahaDB read performance) and by the JVM’s non-heap overhead.

**Host RAM****Recommended -Xmx****Rationale**4 GB2 GB2GB for OS, off-heap, KahaDB file cache8 GB4 GBStandard medium-throughput broker16 GB8 GBHigh-throughput broker32 GB16–18 GBEnterprise multi-destination broker64 GB28–32 GBConsider ZGC at this heap size

### The Fixed-Heap Rule: Always -Xms = -Xmx

The single most impactful JVM memory management change for a message broker is fixing the heap size by setting -Xms equal to -Xmx. With a growing heap (e.g., -Xms512m -Xmx4g), the JVM starts small and expands toward the maximum. Each expansion establishes a new memory ceiling, and the GC works harder to stay under it until it gives up and does a full GC to compact the heap before raising the ceiling again.

A Deloitte engineering study of ActiveMQ documented this precisely: the broker running with a small initial and large maximum heap produced full GC events every 20 minutes and exhibited progressively worse throughput consistency as the heap cycled through 1GB → 2GB → 4GB ceilings. Switching to a fixed 1.28GB heap (-Xms1280m -Xmx1280m) eliminated the ceiling-chasing pattern and reduced full GC frequency significantly.

The fixed-heap also enables -XX:+AlwaysPreTouch, which pre-allocates and zero-fills all JVM heap memory pages at startup. Without pre-touching, large GC events trigger OS page faults as the GC first accesses heap memory that hasn’t been physically backed. Pre-touching moves this work to startup (adding seconds), eliminating it from runtime GC events.

### Kubernetes: Container Memory Alignment

On Kubernetes, the relationship between JVM heap memory usage and container memory limits is critical. Without -XX:+UseContainerSupport (default in JDK 8u191+, all JDK 9+), the JVM reads the host’s total RAM to calculate ergonomic heap sizes. On a 4GB container running on a 64GB host, the JVM attempts to allocate ~32GB of heap, immediately exceeds the container limit and is OOMKilled by Kubernetes with no application-level error.

bash  
\# Container-aware JVM heap memory sizing — two approaches:  
  
\# Approach 1: Explicit size (recommended — predictable, auditable)  
-Xms3g -Xmx3g  
\# For a 4Gi container limit: 3GB heap = 75% of limit; 1GB for off-heap  
  
\# Approach 2: Percentage-based (Java 11+ — adapts to container size changes)  
-XX:MaxRAMPercentage=70.0  
-XX:InitialRAMPercentage=70.0  
  
\# In both cases: ensure container awareness is active  
-XX:+UseContainerSupport # Default since JDK 8u191+

We covered the OOMKill failure mode and the container\_limit × 0.75 formula in detail in our [ActiveMQ on Kubernetes](https://www.meshiq.com/blog/activemq-kubernetes-deployment/) post. The JVM memory sizing and the container memory limit must be explicitly aligned, the JVM does not automatically right-size itself to the container unless UseContainerSupport is active and the container limit is set in the pod spec.

## GC Algorithm Selection

### G1GC: The Production Default

G1GC (-XX:+UseG1GC) has been the JVM default since Java 9. For message brokers, three properties make it the right baseline choice among available GC algorithms:

- **Compacting collector:** Unlike CMS (removed in Java 14), G1GC compacts the heap during collection. This prevents the heap fragmentation that causes CMS to degrade after days or weeks of operation, a critical property for a broker that must run continuously for months.
- **Pause time targeting:** –XX:MaxGCPauseMillis=20 tells G1GC to aim for GC pauses shorter than 20ms. It is a soft goal as G1GC may occasionally exceed it, but it drives the collector to prefer many small collections over occasional large ones. For message delivery, the difference between a 200ms full GC pause and ten 20ms incremental collections is significant.
- **Short-lived object affinity:** ActiveMQ creates enormous volumes of short-lived objects, deserialized frame objects, dispatch table entries, and cursor state that are created and discarded at high rates. G1GC’s Young Generation collection is optimized for this pattern: collect Eden frequently, promote little to Old Gen, and run expensive Old Gen collections rarely.

bash  
\# G1GC production flags for ActiveMQ — with rationale  
  
-XX:+UseG1GC  
  
\# Pause goal: 20ms target reduces delivery gaps from GC events  
-XX:MaxGCPauseMillis=20  
  
\# Trigger concurrent marking at 75% JVM heap memory occupancy  
\# Default is 45% — too aggressive for a broker where Old Gen fills  
\# with long-lived cursor state  
\# Deloitte benchmark: IHOP=75 + NewRatio=4 cut full GC frequency significantly  
-XX:InitiatingHeapOccupancyPercent=75  
  
\# NewRatio=4: Old Gen is 4x the size of Young Gen  
\# For a 2GB heap: Young Gen ~400MB, Old Gen ~1600MB  
\# Appropriate because broker’s dispatch cursor state is long-lived (Old Gen resident)  
-XX:NewRatio=4  
  
\# String deduplication: message destination names and header keys are heavily duplicated  
\# G1GC background thread identifies duplicate strings and consolidates them  
\# Typically reduces JVM memory footprint 5-15% on ActiveMQ workloads  
-XX:+UseStringDeduplication  
  
\# Pre-touch: map all heap pages to physical RAM at startup  
\# Adds startup time (seconds per GB of heap) but eliminates page-fault stalls during GC  
-XX:+AlwaysPreTouch

The NewRatio=4 and InitiatingHeapOccupancyPercent=75 pairing is the key insight from the Deloitte ActiveMQ benchmark. With the default NewRatio=2, too many broker objects (dispatch cursor state, destination metadata) get promoted into Young Gen collections. With NewRatio=4, these long-lived objects stay in Old Gen where they belong, reducing Young Gen GC pressure. With IHOP=75, concurrent marking begins early enough to complete before the JVM heap memory fills, preventing the fallback to stop-the-world full GC.

### ZGC: Sub-Millisecond Pause Requirements

ZGC (-XX:+UseZGC) performs concurrent compaction, all live object relocation happens while application threads run. Stop-the-world phases are reduced to safepoint operations measured in microseconds, not milliseconds. ZGC consistently delivers 0.1–0.5ms pause times regardless of JVM heap memory size.

bash  
\# ZGC configuration — minimal flags (ZGC is largely self-tuning)  
-XX:+UseZGC  
-XX:SoftMaxHeapSize=3500m # Soft heap ceiling for GC budget management  
  
\# Generational ZGC (JDK 21+): significantly better throughput vs. non-generational  
\# In JDK 23+ this is the default; in JDK 21-22 it requires explicit opt-in:  
\# -XX:+ZGenerational

**ZGC trade-offs that matter for ActiveMQ:**

ZGC’s concurrent GC threads compete with broker dispatch threads for CPU. On a CPU-saturated broker (very high message rate, many concurrent producers/consumers), ZGC’s background threads may not get enough CPU to collect the heap fast enough to keep up with the allocation rate.

When this happens, ZGC stalls new object allocations, waiting for GC to free JVM memory, producing latency spikes that are different in character from GC pauses but equally disruptive.

ZGC also does not use compressed object pointers, increasing JVM heap memory usage by 15–30% compared to G1GC for the same workload. Size the heap accordingly.

### Shenandoah: The OpenJDK Alternative

Shenandoah (-XX:+UseShenandoahGC, OpenJDK 12+) achieves concurrent evacuation, objects are moved while application threads run. Pause times are typically 1-5ms with occasional spikes to 10ms. It is a strong choice for OpenJDK-based deployments where ZGC is not available or appropriate. Generational Shenandoah (production-ready in Java 25) improves throughput significantly.

### GC Algorithm Decision Matrix

**Algorithm****JDK Required****Typical Pause****CPU Overhead****Best For ActiveMQ**G1GCJava 9+ (default)10–200ms tunableLowMost production deployments start hereZGCJava 15+ (prod)0.1–1ms+5–10% CPUSLA-gated latency requirements, large heapsShenandoahOpenJDK 12+1–10ms+5–15% CPUOpenJDK deployments needing low latencyCMSRemoved Java 14——Never, deprecated, and removedParallel GCAllHigh varianceLowNever for a broker

## GC Pauses and Message Delivery

Every stop-the-world GC event halts all JVM threads, including every broker dispatch thread, every network I/O thread, and every KahaDB persistence thread. During a GC pause:

- Messages queued for dispatch are not delivered
- Consumer acknowledgments in transit are stalled, preventing message removal from queues
- Producer flow control signals cannot be evaluated, potentially delaying PFC unblock even after space is freed
- KahaDB checkpoint operations miss their scheduled window

For a 200ms full GC pause on a broker processing 10,000 messages per second, approximately 2,000 messages are delayed by 200ms. After the pause, the dispatch backlog is flushed in a burst, which can temporarily exceed consumer processing capacity and stress the consumer side.

**Making GC pauses smaller and less frequent:**

- Fix JVM heap memory size (-Xms = -Xmx) to eliminate growth-phase full GCs
- Set InitiatingHeapOccupancyPercent=75 to trigger concurrent marking before the heap fills, preventing stop-the-world fallback
- Tune NewRatio=4 to right-size the Old Gen for the broker’s long-lived object population
- Monitor full GC frequency with GC logging, a full GC more than once per hour under normal load is a GC tuning signal

## Three OOM Error Types and Their Specific Fixes

The exact OOM error message in the broker log identifies which type you are experiencing. Each type has a different root cause in JVM memory management.

### Type 1: OutOfMemoryError: Java heap space

ERROR | java.lang.OutOfMemoryError: Java heap space  
 | org.apache.activemq.broker.BrokerService | ActiveMQ Broker

The JVM heap memory is exhausted. Three distinct root causes:

**a) Undersized -Xmx:** Increase JVM heap memory using the formula above. Profile under load with JConsole or Java Mission Control to see actual JVM heap memory usage under production message volumes.

**b) Slow consumer message accumulation:** Messages from lagged consumers fill broker memory faster than they are consumed. The fix is not purely to increase heap, it is to address the slow consumer (covered in our [Slow Consumer Detection &amp; Handling](https://www.meshiq.com/blog/activemq-slow-consumer-detection-handling/) and ensure memoryUsage percentOfJvmHeap is configured to trigger producer flow control before OOM occurs.

**c) KahaDB PageFile LRUCache filling the heap** (the most specific and least-documented cause): The KahaDB persistence layer maintains an LRU cache of index page file entries. By default, this cache holds 10,000 entries, and on brokers with many destinations and large persistent message volumes, this cache alone can consume several hundred megabytes of JVM heap memory.

A documented Red Hat support case confirms this scenario: heap dump analysis showed a KahaDBStore instance with its internal PageFile containing a 10,000-entry LRUCache retaining most of the available heap.

The fix is to reduce indexCacheSize in activemq.xml:

xml  
&lt;**persistenceAdapter**&gt;  
 &lt;**kahaDB** directory=”/opt/activemq/data/kahadb”  
 journalMaxFileLength=”67108864″  
 indexCacheSize=”5000″/&gt; &lt;!– Default 10,000 — reduce for memory-constrained brokers –&gt;  
&lt;/**persistenceAdapter**&gt;

### Type 2: OutOfMemoryError: GC overhead limit exceeded

ERROR | java.io.IOException: Unexpected error occurred:  
 java.lang.OutOfMemoryError: GC overhead limit exceeded

The JVM has spent more than 98% of CPU time on GC but recovered less than 2% of heap per cycle, a sign that JVM memory management has broken down entirely. The heap is effectively full, and the GC is running continuously without meaningful progress.

Root causes:

(1) the heap is permanently at or near capacity because message accumulation rate exceeds the GC’s ability to reclaim space;

(2) producer flow control is disabled (producerFlowControl=”false” on destination policy), removing the backpressure that would otherwise prevent heap saturation;

(3) a memory leak in a broker plugin or custom interceptor gradually fills Old Gen until full GC cannot reclaim space.

The fix is not simply to increase -Xmx indefinitely, at high enough JVM memory pressure, the heap will fill again faster than GC can help. The correct fix is to identify what is holding heap references (profile with Java Flight Recorder), ensure producer flow control is enabled, and address the root consumption rate problem.

### Type 3: OutOfMemoryError: unable to create native thread

ERROR | java.lang.OutOfMemoryError: unable to create new native thread

This is not a JVM heap memory problem, the heap may be nearly empty. The OS cannot create additional threads for the JVM, either because the process has reached the OS thread limit (ulimit -u) or because virtual address space for thread stacks is exhausted.

The ActiveMQ-specific root cause: by default, Apache ActiveMQ® creates a dedicated thread per destination (-Dorg.apache.activemq.UseDedicatedTaskRunner=true). On a broker with 500 queues and 500 topics, this creates 1,000 threads. Each thread stack requires 512KB–1MB of native memory (from -Xss). On many Linux servers, the default ulimit -u per process is 4,096 threads.

Fix:

bash  
\# In ACTIVEMQ\_OPTS — convert from O(destinations) threads to shared thread pool  
-Dorg.apache.activemq.UseDedicatedTaskRunner=false

This is the recommended configuration for any broker with more than ~50 destinations. The performance impact is minimal, ActiveMQ’s dispatch logic is already largely asynchronous and does not depend on per-destination threads for correctness.

## GC Logging: The Non-Negotiable Production Setting

Running ActiveMQ without GC logging means having no diagnostic data during a GC-caused incident. GC logs answer questions after an incident: how often were full GCs occurring before the OOM crash, how long were the pause events that coincided with the latency spike, and was the JVM heap memory usage growing toward saturation over the previous 24 hours?

bash  
\# Java 9+ unified logging format (replaces -verbose:gc and -XX:+PrintGCDetails)  
-Xlog:gc\*:file=${ACTIVEMQ\_DATA}/gc.log:time,uptime,level,tags:filecount=5,filesize=20m

This configuration logs all GC events at appropriate detail levels, writes to a rolling file (5 × 20MB = 100MB of history retained), timestamps each event with both wall clock time and JVM uptime, and rotates automatically. On a healthy broker, GC log growth is typically 2–5MB per day.

**Analyzing GC logs:**

- **GCeasy (gceasy.io):** upload the log for instant analysis, pause distributions, heap memory utilization, promotion rates, and full GC frequency
- **Java Mission Control (JMC):** connects live or analyzes JFR recordings with full JVM profiling
- **Java Flight Recorder (JFR):** continuous low-overhead production profiling that captures GC events, thread activity, and JIT compilation together

**Key metrics to review in GC logs:**

- Young GC pause: should be &lt;20ms with MaxGCPauseMillis=20
- Full GC frequency: more than once per hour under normal load is a GC tuning signal
- Old Gen occupancy after major GC: &gt;70% after collection means the JVM heap memory needs to grow or the Old Gen population needs to shrink

## Complete Production JVM Configuration Reference

The following is a production-validated configuration for Apache ActiveMQ® on a dedicated 8GB host:

bash  
\# === ACTIVEMQ\_OPTS — Production-validated for Classic 5.18.x, dedicated 8GB host ===  
  
ACTIVEMQ\_OPTS\_MEMORY=”-Xms4g -Xmx4g”  
\# 4GB JVM heap memory = 50% of 8GB host — leaves room for OS, off-heap, KahaDB page cache  
  
ACTIVEMQ\_OPTS=”$ACTIVEMQ\_OPTS\_MEMORY \\  
 -XX:+UseG1GC \\  
 -XX:MaxGCPauseMillis=20 \\  
 -XX:InitiatingHeapOccupancyPercent=75 \\  
 -XX:NewRatio=4 \\  
 -XX:+UseStringDeduplication \\  
 -XX:+AlwaysPreTouch \\  
 -XX:MaxMetaspaceSize=512m \\  
 -Dorg.apache.activemq.UseDedicatedTaskRunner=false \\  
 -XX:+UseContainerSupport \\  
 -Xlog:gc\*:file=\\${ACTIVEMQ\_DATA}/gc.log:time,uptime,level,tags:filecount=5,filesize=20m \\  
 -XX:+HeapDumpOnOutOfMemoryError \\  
 -XX:HeapDumpPath=\\${ACTIVEMQ\_DATA}/heap-dump.hprof \\  
 -XX:OnOutOfMemoryError=’kill -9 %p'”  
export ACTIVEMQ\_OPTS

For a latency-sensitive deployment requiring sub-millisecond GC pauses (JDK 15+):

bash  
ACTIVEMQ\_OPTS\_MEMORY=”-Xms4g -Xmx4g”  
  
ACTIVEMQ\_OPTS=”$ACTIVEMQ\_OPTS\_MEMORY \\  
 -XX:+UseZGC \\  
 -XX:SoftMaxHeapSize=3500m \\  
 # Generational ZGC — JDK 21+: -XX:+ZGenerational (default in JDK 23+)  
 -XX:MaxMetaspaceSize=512m \\  
 -Dorg.apache.activemq.UseDedicatedTaskRunner=false \\  
 -XX:+UseContainerSupport \\  
 -Xlog:gc\*:file=\\${ACTIVEMQ\_DATA}/gc.log:time,uptime,level,tags:filecount=5,filesize=20m \\  
 -XX:+HeapDumpOnOutOfMemoryError \\  
 -XX:HeapDumpPath=\\${ACTIVEMQ\_DATA}/heap-dump.hprof”  
export ACTIVEMQ\_OPTS

## **ActiveMQ JVM Tuning Review for Your Production Environment JVM memory**

configuration errors are silent until catastrophic – OOM crashes, multi-second GC pauses, and Kubernetes OOMKill events with no application-level errors. meshIQ’s team has tuned ActiveMQ JVM memory management configurations across hundreds of enterprise deployments and can review your current settings against your workload profile and OOM history.

[****Request a JVM Review****](https://www.meshiq.com/apache-activemq/enterprise-support/)



## JVM Tuning Checklist for ActiveMQ Production

**Heap Sizing:**

- -Xms = -Xmx (fixed **JVM heap memory** – no growth-phase GC events)
- -Xmx ≤ 50–60% of physical RAM (bare metal) or 70–75% of container limit (K8s)
- -XX:+UseContainerSupport enabled (for K8s/Docker – default in JDK 8u191+)

**GC Configuration:**

- -XX:+UseG1GC (or -XX:+UseZGC for sub-millisecond requirements on JDK 15+)
- -XX:MaxGCPauseMillis=20 (G1GC pause target — core **GC tuning** lever)
- -XX:InitiatingHeapOccupancyPercent=75 (deferred concurrent marking — Deloitte-validated)
- -XX:NewRatio=4 (right-size Old Gen for long-lived broker objects)
- -XX:+UseStringDeduplication (G1GC — reduce **JVM memory** footprint from message header duplication)
- -XX:+AlwaysPreTouch (eliminate runtime page faults from GC)
- -XX:MaxMetaspaceSize=512m (cap Metaspace growth)

**Thread Management:**

- -Dorg.apache.activemq.UseDedicatedTaskRunner=false (thread pool for many destinations)

**Observability:**

- GC logging enabled: -Xlog:gc\*:file=… with rolling files
- -XX:+HeapDumpOnOutOfMemoryError + -XX:HeapDumpPath configured
- -XX:OnOutOfMemoryError=’kill -9 %p’ (let supervisor restart the process)

**Configuration Alignment:**

- activemq.xml memoryUsage percentOfJvmHeap=”70″ consistent with **JVM heap memory** size
- kahaDB indexCacheSize reviewed if OOM heap dumps show LRUCache dominance

## JVM Memory Configuration Is Infrastructure

The ACTIVEMQ\_OPTS file is as important as activemq.xml. The JVM memory management configuration in this guide is a production-validated starting point, profile under your actual workload with GCeasy or Java Mission Control, monitor full GC frequency in the first weeks of production, and adjust **GC tuning** parameters like InitiatingHeapOccupancyPercent and NewRatio based on your observed Old Gen occupancy patterns.

**Get your ActiveMQ JVM configuration reviewed → [Request a JVM Review](https://www.meshiq.com/apache-activemq/enterprise-support/)**

### **Frequently Asked Questions**

**Q1: What is the recommended JVM heap memory size for ActiveMQ?**

Start with 2–4GB JVM heap memory for typical production workloads, not exceeding 50–60% of available physical RAM. For Kubernetes, set -Xmx to 70–75% of the container memory limit. Always set -Xms = -Xmx for fixed JVM heap memory, this is the single most impactful JVM memory management change you can make.

**Q2: Which GC algorithm is best for ActiveMQ?**

Among the available GC algorithms, G1GC is the right choice for most deployments, the JVM default since Java 9, compacting, and pause-time tunable. ZGC (JDK 15+) for sub-millisecond pause requirements. Never use CMS (removed in Java 14) or Parallel GC for a message broker. Start with G1GC GC tuning before considering ZGC.

**Q3: How do I set JVM options for ActiveMQ?**

Apache ActiveMQ®: ACTIVEMQ\_OPTS in bin/env. Apache Artemis™: JAVA\_ARGS in etc/artemis.profile. Never edit the main launch scripts directly, edits are lost on upgrade.

**Q4: What causes OutOfMemoryError in ActiveMQ?**

Three distinct JVM memory types with different fixes: (1) Java heap space, undersized JVM heap memory, slow consumer accumulation, or KahaDB indexCacheSize LRUCache; (2) GC overhead limit exceeded, JVM heap memory usage permanently full, a GC tuning failure; (3) unable to create native thread, thread exhaustion from UseDedicatedTaskRunner=true with many destinations.

**Q: How does GC affect ActiveMQ message delivery?**

Stop-the-world GC events halt all broker threads, including dispatch. A 200ms full GC = 200ms delivery silence. The fix combines proper JVM heap memory sizing, MaxGCPauseMillis=20 GC tuning, and InitiatingHeapOccupancyPercent=75. For stricter requirements, switch GC algorithms from G1GC to ZGC.