Backend Development 18 min read

Investigation of Log4j2 Asynchronous Logging Memory Growth and Mitigation Strategies

This article analyzes the memory increase observed after enabling Log4j2 asynchronous logging in Java services, examines root causes such as thread‑local reuse and large char[] allocations, and presents configuration and code changes—including disabling thread‑locals and limiting message size—to prevent heap growth and improve performance.

JD Tech
JD Tech
JD Tech
Investigation of Log4j2 Asynchronous Logging Memory Growth and Mitigation Strategies

During a recent OpsReview, the team noticed that enabling Log4j2 asynchronous logging caused host disk I/O saturation and increased TP99 response times. Initial investigation revealed that asynchronous logging creates large char[] arrays when log messages exceed a certain length, leading to heap memory growth.

The root cause was identified in Log4j2's handling of messageText: when ENABLE_THREADLOCALS is true, messages longer than MAX_REUSABLE_MESSAGE_SIZE (default 518 characters) are trimmed by creating new char[] arrays via Arrays.copyOf . These arrays can survive multiple Young Generation GCs and eventually reside in the Old generation, causing heap consumption that triggers Full GCs.

Configuration changes were tested:

# All Logger asynchronous configuration
log4j2.contextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector
log4j2.asyncQueueFullPolicy=Discard
log4j2.discardThreshold=ERROR

Disabling thread‑locals with log4j2.enable.threadlocals=false prevented the reuse of messageText and allowed the char[] arrays to be collected during Young GCs, eliminating the gradual heap increase.

log4j2.enable.threadlocals=false

Additional mitigations include increasing log4j.maxReusableMsgSize to match typical log lengths, and truncating log output to a reasonable size (e.g., 256 characters) to avoid creating oversized char[] objects.

log4j.maxReusableMsgSize=1024

Performance monitoring after applying these changes showed stable heap usage and no further Full GCs, confirming the effectiveness of the solutions.

The article also discusses the default RingBuffer size (4096 when thread‑locals are enabled) and references official Log4j2 documentation on asynchronous logging and garbage‑free features.

JavaJVMperformanceMemory Leaklog4jGCasynchronous logging
JD Tech
Written by

JD Tech

Official JD technology sharing platform. All the cutting‑edge JD tech, innovative insights, and open‑source solutions you’re looking for, all in one place.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.