Memory Management

July 2, 2026 · View on GitHub

This document covers how MockServer manages memory for log entries and expectations, how default limits are calculated, and how to tune them for your workload.

Overview

MockServer stores two main categories of data in memory:

  1. Log entries — recorded requests, matched expectations, forwarded requests, verification results, and operational log messages
  2. Expectations — request matchers and their associated response/forward actions

Both are stored in bounded circular data structures that evict the oldest entries when full. The default size limits are computed dynamically based on available JVM heap memory.

graph TB
    subgraph "JVM Heap"
        subgraph "Log Entry Storage"
            RB["LMAX Disruptor Ring Buffer
            Pre-allocated LogEntry slots
            Size: nextPowerOfTwo(ringBufferSize)
            default min(maxLogEntries, 16384)"]
            EL["CircularConcurrentLinkedDeque
            Persistent event store
            Count bound: maxLogEntries
            Byte bound: maxEventLogSizeInBytes"]
        end
        subgraph "Expectation Storage"
            PQ["CircularPriorityQueue
            Max size: maxExpectations"]
        end
        OTHER["Netty buffers, thread stacks,
        class metadata, GC overhead"]
    end

    RB -->|"cloneAndClear()"| EL
    EL -->|"evict oldest → clear()"| GC[GC eligible]

Default Limit Calculation

Both maxLogEntries and maxExpectations are computed from available heap at the time the property is first read.

Formula

heapAvailableInKB = (maxHeap - usedHeap) / 1024 - 20480

maxLogEntries    = min(heapAvailableInKB / 8, 100000)
maxExpectations  = min(heapAvailableInKB / 10, 15000)
ParameterValuePurpose
Base memory reservation20 MB (20,480 KB)Reserved for JVM internals, Netty buffers, thread stacks
Per-log-entry estimate8 KBEstimated heap cost per stored log entry (see analysis below)
Per-expectation estimate10 KBEstimated heap cost per stored expectation including matcher (see analysis below)
Log entry hard cap100,000Upper bound regardless of heap
Expectation hard cap15,000Upper bound regardless of heap

Source Code

ComponentFileMethod
Heap available probeConfigurationProperties.javaheapAvailableInKB()
Undefined-max-robust heap calcConfigurationProperties.javacomputeHeapAvailableInKB(long, long, long, long)
Heap-based default with floorConfigurationProperties.javaheapBasedDefaultOrFloor(long, long, int, int)
maxLogEntries() defaultConfigurationProperties.javamaxLogEntries()
maxExpectations() defaultConfigurationProperties.javamaxExpectations()
Ring buffer sizingConfiguration.javaringBufferSize() (resolves field → property → min(maxLogEntries, 16384))
Ring buffer default resolutionConfigurationProperties.javaresolveRingBufferSize(int)
Heap measurementMemoryMonitoring.javagetJVMMemory()

Example: Default Limits by Heap Size

The table below shows the computed defaults for different JVM heap configurations, assuming 30% of heap is used at the time the property is first read.

Max Heap (-Xmx)Used at Startup (~30%)Available KBDefault maxLogEntriesDefault maxExpectations
64 MB19 MB24,2563,0322,425
128 MB38 MB71,5528,9447,155
256 MB77 MB162,04820,25615,000 (capped)
512 MB154 MB346,11243,26415,000 (capped)
1 GB307 MB711,16888,89615,000 (capped)
2 GB614 MB1,401,856100,000 (capped)15,000 (capped)
4 GB1,229 MB2,843,648100,000 (capped)15,000 (capped)

With the default Docker image (no -Xmx set, JVM defaults to ~256 MB), users get roughly 20,000 log entries. Since each HTTP request generates 2-3 log entries (RECEIVED_REQUEST + EXPECTATION_MATCHED + EXPECTATION_RESPONSE), this means approximately ~7,000-10,000 HTTP requests are retained before eviction begins.

Dev Mode Override

When devMode is enabled (--dev CLI flag, -Dmockserver.devMode=true, or MOCKSERVER_DEV_MODE=true), maxLogEntries and maxExpectations default to 1,000 instead of the heap-based formula above. This reduces memory usage for laptop and test-suite workloads. If either property is explicitly set (via system property, environment variable, or properties file), the explicit value takes precedence over the dev-mode default. See configuration-reference.md for the full property definition.

Shared Heap Pool

Both maxLogEntries and maxExpectations are calculated independently from the same available heap. In the worst case (both buffers completely full with average-sized entries), the combined memory usage could exceed the available heap. In practice this is mitigated by:

  • The circular eviction means buffers rarely fill completely — older entries are GC'd as new ones arrive
  • Most users have far fewer expectations than the cap allows
  • The JVM's garbage collector reclaims memory from evicted entries promptly

On small heaps (< 256 MB), if you have both a large number of expectations AND high request volume, set explicit values for both properties rather than relying on the computed defaults.

Timing Sensitivity

heapAvailableInKB() measures the current free heap at the moment the property is first read. During JVM startup, the heap may be more heavily used (class loading, initialisation) than during steady state. This means the computed default can be lower than what the JVM can actually sustain. After garbage collection runs and startup objects are freed, significantly more heap may be available.

Undefined Heap Max (JMX getMax() = -1)

The JMX spec allows MemoryUsage.getMax() to return -1 (undefined). In some environments the aggregated heap-pool max reports as -1/0 — verified on a GraalVM native image of the shaded jar, and possible in exotic JVM/WAR setups. Left unguarded, (max - used) / 1024 - 20480 would then be a large negative number, driving maxExpectations/maxLogEntries to <= 0 — so the expectation store and log ring buffer silently drop everything (PUT /mockserver/expectation returns 201 but the expectation never matches; "Log event ring buffer full" at startup).

heapAvailableInKB() is therefore robust to an undefined heap max (see computeHeapAvailableInKB(...)):

  1. When the aggregated JMX heap max is <= 0, it falls back to Runtime.maxMemory() for the ceiling and totalMemory() - freeMemory() for used. (Runtime.maxMemory() may be Long.MAX_VALUE when the heap is unbounded — the subtraction stays non-negative and the very large result is clamped by the min(..., cap) in the callers.)
  2. When neither JMX nor Runtime yields a usable ceiling, it returns 0.
  3. The result is floored at 0 — never negative.

The getters then apply a lower bound so a 0 heap-derived value still yields a functional store (see heapBasedDefaultOrFloor(...)): when min(heapAvailableInKB / perEntryKB, cap) computes to <= 0, the default falls back to the dev-mode default (1,000) for that property rather than 0. Explicitly setting mockserver.maxExpectations / mockserver.maxLogEntries always overrides this.

Log Entry Memory Analysis

LogEntry Object Graph

Each LogEntry holds references to the HTTP request, response, expectation, and formatting data associated with an event.

graph LR
    LE["LogEntry
    ~112 B shell"]
    LE --> ID["id: String
    UUID, ~112 B"]
    LE --> CID["correlationId: String
    UUID, ~112 B"]
    LE --> TS["timestamp: String
    lazy, ~86 B"]
    LE --> MF["messageFormat: String
    ~78 B"]
    LE --> ARGS["arguments: Object array
    shallow clones of req/resp"]
    LE --> REQ["httpRequests: RequestDefinition[]"]
    LE --> RESP[httpResponse: HttpResponse]
    LE --> EXP[expectation: Expectation]

    REQ --> HR["HttpRequest
    ~108 B shell"]
    HR --> METHOD["method: NottableString
    ~176 B"]
    HR --> PATH["path: NottableString
    ~200 B"]
    HR --> HDRS["headers: Headers
    ~3 KB for 6 headers"]
    HR --> BODY["body: Body
    variable"]
    HR --> SA["socketAddress
    ~116 B"]

    RESP --> SC[statusCode: Integer]
    RESP --> RP[reasonPhrase: String]
    RESP --> RHDRS[headers: Headers]
    RESP --> RBODY[body: BodyWithContentType]

Field-Level Size Estimates

LogEntry Shell (~112 bytes)

FieldTypeBytesNotes
Object header16
hashCodeint4
idString ref4UUID string allocated separately (~112 B)
correlationIdString ref4UUID string (~112 B)
portInteger ref4Boxed int (16 B when non-null)
logLevelLevel ref4Enum singleton
alwaysLogboolean1
epochTimelong8
timestampString ref4Lazy, ~86 B when materialised
typeLogMessageType ref4Enum singleton
httpRequestsRequestDefinition[] ref4
httpUpdatedRequestsRequestDefinition[] ref4Lazy shallow clone
httpResponseHttpResponse ref4
httpUpdatedResponseHttpResponse ref4Lazy shallow clone
httpErrorHttpError ref4Usually null
expectationExpectation ref4
expectationIdString ref4UUID (~112 B)
throwableThrowable ref4Usually null
consumerRunnable ref4Always null in stored entries
deletedboolean1
messageFormatString ref4~78 B
messageString ref4Lazy
argumentsObject[] ref4
becauseString ref4Usually null
(padding)~4Alignment

HttpRequest (~3.5-5 KB typical)

ComponentTypical SizeNotes
HttpRequest shell (19 fields)~108 BInherits from NotObjectWithJsonToString
method (NottableString)~176 Be.g., "GET" — NottableString + value String + json String
path (NottableString)~200 Be.g., "/api/users"
headers (Headers + Guava LinkedHashMultimap)~3,000 B6 typical headers (Host, Content-Type, Accept, User-Agent, Content-Length, Connection)
body (StringBody, if present)0-2,000 BNull for GET; ~630 B for 100-char JSON; ~1,800 B for 500-char JSON
socketAddress~116 Bhost String + port Integer + scheme enum
localAddress / remoteAddress~140 BTwo short strings
keepAlive / secure~32 BTwo boxed Booleans
Total (GET, no body)~3,800 B
Total (POST, 200-char body)~4,700 B

NottableString (~176-284 bytes each)

Each NottableString wraps a value with optional negation and regex support:

ComponentBytes
Object header + 5 fields~48 B
value String (short, e.g., 3-15 chars)~64-100 B
json String (same content)~64-100 B
Total (short value like "GET")~176 B
Total (medium value like "application/json")~250 B

Headers (~500 bytes per header pair)

Each header is a key-value pair of NottableString objects stored in a Guava LinkedHashMultimap:

ComponentPer-Header Bytes
Key NottableString~176 B
Value NottableString~240 B
Multimap entry overhead~64 B
Total per header~480 B

The Headers object itself adds ~232 B of overhead (shell + multimap base structure).

HttpResponse (~3.5 KB typical)

ComponentTypical SizeNotes
HttpResponse shell (8 fields)~76 BInherits from Action
statusCode (Integer)~16 BBoxed int
reasonPhrase (String)~44 Be.g., "OK"
body (StringBody)~630-1,800 B100-500 char JSON body
headers (Headers)~2,600 B5 typical response headers
Total (200-char JSON body)~3,400 B

StringBody (~630 bytes for 100-char body)

ComponentBytes
StringBody shell (inherits 4 levels)~72 B
`value$ \text{String}~240 \text{B} (100 \text{chars} \times 2 \text{bytes} + 40 \text{B} \text{overhead})
$rawBytes` byte array~116 B (100 bytes + 16 B header)
contentType MediaType~200 B
Total (100-char body)~630 B
Total (500-char body)~1,800 B
Total (2 KB body)~4,700 B

Per Log Entry Type Estimates

Each log entry type populates a different subset of fields. These estimates assume a typical HTTP request with 6 headers and a small-to-medium JSON body.

Log Entry TypeTypical MemoryWhat It Stores
RECEIVED_REQUEST (GET)~4.2 KBLogEntry shell + strings + HttpRequest
RECEIVED_REQUEST (POST, 200-char body)~5.2 KBAs above + request body
EXPECTATION_MATCHED~7.8 KBLogEntry + HttpRequest + Expectation ref (shared, not copied)
EXPECTATION_RESPONSE~7.9 KBLogEntry + HttpRequest + HttpResponse
FORWARDED_REQUEST~10.2 KBLogEntry + HttpRequest + HttpResponse + Expectation wrapper
NO_MATCH_RESPONSE~6.5 KBLogEntry + HttpRequest + 404 response
CREATED_EXPECTATION~4.9 KBLogEntry + expectation definition
INFO / WARN / ERROR~0.5 KBLogEntry shell + message format + arguments

Per HTTP Transaction Memory

Each inbound HTTP request generates 2-3 log entries:

ScenarioLog Entries CreatedTotal Memory
GET matched to expectationRECEIVED_REQUEST + EXPECTATION_MATCHED + EXPECTATION_RESPONSE~20 KB
POST matched to expectationRECEIVED_REQUEST + EXPECTATION_MATCHED + EXPECTATION_RESPONSE~22 KB
Proxied/forwarded requestRECEIVED_REQUEST + FORWARDED_REQUEST~14 KB
No match (404)RECEIVED_REQUEST + NO_MATCH_RESPONSE~11 KB

Arguments Array and Shallow Clones

The arguments field in LogEntry stores objects used for message formatting. When setArguments() is called with HttpRequest or HttpResponse objects, it creates shallow clones with updated body representations (LogEntryBody). These shallow clones share the same headers, cookies, pathParameters, and queryStringParameters references as the originals — only the body wrapper is replaced. This adds approximately 200-400 bytes per clone rather than duplicating the entire request/response.

Byte-Budget Eviction (maxEventLogSizeInBytes)

The CircularConcurrentLinkedDeque supports a second, independent bound: a body-byte budget supplied via the maxEventLogSizeInBytes configuration property (default 0 = disabled).

Why it exists

maxLogEntries caps the number of log entries, not their size. When each entry holds a large body — for example, an LLM exchange with a multi-hundred-kilobyte tool schema or conversation context — a few thousand entries can exhaust the heap even though the count is low. The byte budget provides a size-based safety valve that the count bound cannot express.

How it works

On construction, MockServerEventLog passes maxEventLogSizeInBytes and LogEntry::estimatedHeapSize (the weigher) to the 4-argument CircularConcurrentLinkedDeque constructor:

this.eventLog = new CircularConcurrentLinkedDeque<>(
    configuration.maxLogEntries(),
    configuration.maxEventLogSizeInBytes(),
    LogEntry::estimatedHeapSize,
    LogEntry::clear);

On every add(), evictExcessElements(weight) runs two passes:

  1. Count pass — evict oldest until count < maxSize (existing count bound).
  2. Byte pass — when maxBytes > 0 and a weigher is set, evict oldest until totalBytes + incomingWeight <= maxBytes.

A running AtomicLong totalBytes tracks the sum of all retained entry weights, updated on every insert and eviction. A single element whose weight alone exceeds maxBytes is still admitted — the byte loop stops when the deque is empty rather than rejecting the incoming entry.

LogEntry.estimatedHeapSize()

The weigher used by the byte budget. Returns a stable lower-bound estimate of the bytes this entry retains on the heap, counting only primary request and response body bytes (the dominant cost for large LLM-capture exchanges):

public long estimatedHeapSize() {
    // counts httpRequests[*].getBodyAsRawBytes().length + httpResponse.getBodyAsRawBytes().length
}

The value is computed lazily and cached (-1 until first call) so the weight is identical at add-time and at evict-time. It deliberately excludes lazily-derived httpUpdated* copies and the arguments array so it never changes after the entry is built — the deque relies on add-time weight matching evict-time weight exactly.

Actual heap retention is a small multiple of this figure (headers, metadata, UUID strings, etc.). Set maxEventLogSizeInBytes well under the JVM heap — for a 2 GB heap, 256 MB is a reasonable starting point.

Body truncation (maxLoggedBodyBytes)

maxLoggedBodyBytes is a secondary in-memory valve (default 0 = unlimited). When set to a positive value, MockServerEventLog.truncateBodiesForLog() replaces the request and/or response body in the (already-cloned) log entry with a truncated copy before the entry is added to the deque. A x-mockserver-body-truncated: <originalLength> header marks the truncated copy.

Key ordering guarantee: disk capture (when persistRecordedRequestsToDisk is enabled) runs before truncation in processLogEntry, so the NDJSON archive always receives the full body regardless of maxLoggedBodyBytes. The archive captures both forwarded and mocked exchanges and outlives ring-buffer (maxLogEntries / maxEventLogSizeInBytes) eviction and process restarts; evicted entries can be brought back into the queryable in-memory log via PUT /mockserver/import?format=recording (?source=disk to read the configured path). See event-system.md for the disk-capture and re-import paths.

The byte-budget weigher measures the (possibly truncated) body bytes, so truncation reduces the weight contributed to totalBytes.

Eviction and GC

When the CircularConcurrentLinkedDeque reaches capacity (count or byte budget):

  1. The oldest LogEntry is removed from the deque via an internal pollAndEvict()
  2. totalBytes is decremented by the evicted entry's weight (before the callback, in case the callback clears the entry)
  3. The onEvictCallback calls LogEntry.clear(), which nulls all reference fields
  4. All child objects (HttpRequest, HttpResponse, Strings, etc.) become eligible for garbage collection
  5. The LogEntry object itself is also GC-eligible (it is fully removed from the deque)

The LMAX Disruptor ring buffer pre-allocates LogEntry slots separately. Data is copied into ring buffer slots via translateTo(), then the consumer calls cloneAndClear() — creating a new LogEntry for persistent storage and clearing the ring buffer slot. Ring buffer slots do not contribute to persistent memory usage.

O(1) capacity check (CPU). The eviction check on every insert uses an internal AtomicInteger size counter, not ConcurrentLinkedDeque.size() (which is O(n) — it walks the whole list). This matters because the check runs on the hot path for every log entry: with the O(n) call, once the log was full each insert cost ~O(maxLogEntries) and CPU climbed as the log filled (GitHub issue #2329). With the counter, insert/evict/size() are O(1). If you change CircularConcurrentLinkedDeque, keep all mutators updating the counter (see its javadoc) so size() stays accurate.

Clearing expectations does NOT clear the log. PUT /mockserver/clear?type=EXPECTATIONS only clears stored expectations; the request/event log is independent and keeps its entries (bounded by maxLogEntries and maxEventLogSizeInBytes). To free the log, use PUT /mockserver/clear?type=LOG (or ?type=ALL), or PUT /mockserver/reset (clears both). Long-running, high-throughput servers should either lower maxLogEntries, set a byte budget, or periodically clear the log.

Expectation Memory Analysis

Stored expectations are heavier than they first appear because each expectation creates a matcher wrapper with sub-matchers for every field.

Objects Per Stored Expectation

ComponentTypical SizeNotes
Expectation shell~400 Bid, times, timeToLive, priority, 10 action refs
HttpRequest (matcher definition)~400-600 BUsually just method + path, fewer headers than a real request
HttpResponse (response action)~500 B - 50+ KBDominated by response body size
HttpRequestPropertiesMatcher~900-1,200 BWrapper with sub-matchers for each field
Container overhead (CircularPriorityQueue)~350 BConcurrentLinkedQueue + ConcurrentSkipListSet + ConcurrentHashMap entries
Total (simple, small response body)~3.5 KB
Total (medium, 2 KB response body)~6-7 KB
Total (large, 10 KB response body)~15-20 KB
Total (very large, 50 KB response body)~55-75 KB

The 75 KB per-expectation estimate in the default formula targets the worst case (large response bodies). For most workloads with small responses, it is 10-20x too conservative.

How the Estimates Were Chosen

The per-entry estimates (8 KB for log entries, 10 KB for expectations) are based on the field-level analysis in the sections above. They target the realistic weighted average for typical API mocking workloads (small-to-medium JSON bodies, a handful of headers), with a modest safety margin.

MetricEstimate UsedRealistic AverageWorst Case
Per log entry8 KB6-8 KB30+ KB (large bodies)
Per expectation10 KB3.5-7 KB75+ KB (huge response bodies)

For workloads with very large request/response bodies (>10 KB), the automatic defaults may over-provision. Users with such workloads should set explicit values using the tuning guide below, and monitor memory via outputMemoryUsageCsv.

History: Prior to this change, the estimates were 80 KB per log entry and 75 KB per expectation — values that were 10-16x too conservative for typical workloads, resulting in unnecessarily low limits and unexpected log eviction (GitHub issue #1285).

Tuning Guide

Configuration Properties

PropertySystem PropertyEnvironment VariableDefault
Max log entriesmockserver.maxLogEntriesMOCKSERVER_MAX_LOG_ENTRIESmin(heapAvailableKB / 8, 100000)
Max event log size (bytes)mockserver.maxEventLogSizeInBytesMOCKSERVER_MAX_EVENT_LOG_SIZE_IN_BYTES0 (disabled)
Max logged body bytesmockserver.maxLoggedBodyBytesMOCKSERVER_MAX_LOGGED_BODY_BYTES0 (unlimited)
Ring buffer sizemockserver.ringBufferSizeMOCKSERVER_RING_BUFFER_SIZEmin(maxLogEntries, 16384) (rounded up to a power of two)
Max expectationsmockserver.maxExpectationsMOCKSERVER_MAX_EXPECTATIONSmin(heapAvailableKB / 10, 15000)

Properties are resolved in this order (first match wins):

  1. In-memory property cache (set programmatically)
  2. Java system property (-Dmockserver.maxLogEntries=...)
  3. Properties file (mockserver.properties)
  4. Environment variable (MOCKSERVER_MAX_LOG_ENTRIES)
  5. Computed default

Choosing Values

Step 1: Estimate your per-entry memory

Your workloadEstimated per-entry sizeEntries per HTTP request
Small API responses (< 1 KB bodies)5-8 KB2-3
Medium API responses (1-5 KB bodies)8-15 KB2-3
Large API responses (5-50 KB bodies)15-50 KB2-3
Proxy mode (request + response stored)10-30 KB2

Step 2: Calculate how many entries your heap can support

available_heap_MB = Xmx - (estimated_used_heap_MB + 50 MB safety margin)
entries = (available_heap_MB * 1024) / per_entry_KB

Step 3: Account for entries per HTTP request

http_requests_retained = entries / entries_per_request

Examples

Docker container, 256 MB heap, small API mocking:

available = 256 - (80 + 50) = 126 MB = 129,024 KB
per_entry = 8 KB (typical small API)
max_log_entries = 129,024 / 8 = ~16,000
http_requests_retained = 16,000 / 3 = ~5,300

Set: MOCKSERVER_MAX_LOG_ENTRIES=16000

Docker container, 512 MB heap, medium API responses:

available = 512 - (150 + 50) = 312 MB = 319,488 KB
per_entry = 12 KB
max_log_entries = 319,488 / 12 = ~26,000

Set: MOCKSERVER_MAX_LOG_ENTRIES=26000

Large test suite, 2 GB heap, needs to retain all requests:

available = 2048 - (400 + 50) = 1,598 MB = 1,636,352 KB
per_entry = 8 KB
max_log_entries = 1,636,352 / 8 = ~204,000

Set: MOCKSERVER_MAX_LOG_ENTRIES=200000

Memory Monitoring

Enable CSV memory tracking to observe actual memory usage under your workload:

mockserver.outputMemoryUsageCsv=true
mockserver.memoryUsageCsvDirectory=/tmp/mockserver-metrics

This writes a memoryUsage_YYYY-MM-DD.csv file every 50 log or expectation updates with columns including eventLogSize, maxLogEntries, heapUsed, and heapMaxAllowed. Use this to validate that your maxLogEntries setting is appropriate for your heap size.

Ring Buffer Sizing

The LMAX Disruptor ring buffer is the in-flight buffer between the producing Netty I/O threads and the single consumer thread. It is decoupled from maxLogEntries (which bounds the separate retained event history in the CircularConcurrentLinkedDeque). The ring only needs to absorb short bursts of log events, not hold the full retained history, so it has its own knob, ringBufferSize.

Its size is computed as the next power of two greater than the resolved ringBufferSize, which defaults to min(maxLogEntries, 16384):

maxLogEntriesResolved ringBufferSize (default)Ring Buffer Size (power of two)Ring Buffer Memory (empty LogEntry shells)
1,0001,0001,024~115 KB
5,0005,0008,192~920 KB
10,00010,00016,384~1.8 MB
50,00016,384 (capped)32,768~3.7 MB
100,00016,384 (capped)32,768~3.7 MB

Before this decoupling, maxLogEntries=100000 forced a 131,072-slot ring (~14.7 MB of empty LogEntry shells) purely as a side effect of retention sizing; the default 16,384 ceiling caps that at ~3.7 MB while leaving small deployments (maxLogEntries ≤ 16,384) unchanged.

The ring buffer pre-allocates LogEntry objects (just the shells, ~112 bytes each). These are reused via translateTo() / cloneAndClear() and do not hold persistent data. The ring buffer memory is a fixed overhead that does not grow with request volume.

Why a separate ringBufferSize knob?

The ring buffer absorbs the rate gap between producers and the single consumer thread — it must be large enough that bursts of concurrent log writes do not overflow it (an overflow drops the event and increments mock_server_dropped_log_events; see event-system.md). That gap is a function of throughput, not of how long you retain history. Slaving the ring to maxLogEntries therefore over-provisioned the ring for high-retention/low-burst deployments.

  • Default min(maxLogEntries, 16384) — small deployments keep their previous ring exactly; large retention settings stop inflating the ring.
  • Raise it only if you observe dropped log events (mock_server_dropped_log_events non-zero and growing) under sustained extreme load.
  • Lower it to shave fixed memory if you have a low-throughput, high-retention workload.

Configure it via mockserver.ringBufferSize, the MOCKSERVER_RING_BUFFER_SIZE environment variable, or Configuration.ringBufferSize(int). The value is rounded up to the next power of two (a Disruptor requirement). The nextPowerOfTwo() method in Configuration.java supports values up to 2^30 = 1,073,741,824.