Analyzing Log4j2 Thread Blocking Issues and Best Practices

The article examines how Log4j2’s AsyncAppender can block threads when its default 128‑entry queue fills, because creating LogEvent snapshots triggers ThrowableProxy parsing that loads reflective and lambda‑generated classes, causing class‑loader bottlenecks, and recommends custom async appenders, disabling reflection inflation, using synchronous loggers, and proper configuration to avoid these delays.

Meituan Technology Team
Meituan Technology Team
Meituan Technology Team
Analyzing Log4j2 Thread Blocking Issues and Best Practices

This article investigates several thread‑blocking problems caused by the Apache Log4j2 logging framework in a high‑throughput Java service. The authors describe how excessive log volume can fill the internal queue of AsyncAppender, leading to blocked threads, and how the framework’s handling of exceptions triggers class loading that further aggravates the issue.

Key findings include:

When the AsyncAppender queue is full, the logging thread may block while trying to enqueue a new LogEvent. The default queue size is 128, and the blocking behavior can be configured via log4j2.AsyncQueueFullPolicy.

Creating a LogEvent snapshot involves constructing a ThrowableProxy, which parses the exception stack trace. This process loads classes (including JAR metadata) using reflection, potentially invoking the JVM’s “inflation” mechanism that generates bytecode for reflective calls.

The JVM’s reflection inflation creates classes named sun.reflect.GeneratedMethodAccessor<N>. These dynamically generated classes cannot be loaded by the application’s WebAppClassLoader, causing synchronized class‑loading bottlenecks.

Lambda expressions generate synthetic classes named $$Lambda$. In JDK 8 versions prior to the fix, loading these classes also suffers from the same class‑loader limitation, leading to additional thread blocking.

The AsyncLoggerConfig uses a Disruptor ring buffer. When a MutableLogEvent is translated for the ring buffer, it also creates a ThrowableProxy, repeating the class‑loading problem.

Sample code excerpts illustrate the problematic paths:

public void append(LogEvent logEvent) {
    if (!isStarted()) {
        throw new IllegalStateException("AsyncAppender " + getName() + " is not active");
    }
    Log4jLogEvent memento = Log4jLogEvent.createMemento(logEvent, includeLocation);
    if (!transfer(memento)) {
        // queue full handling
    }
}
public ThrowableProxy(Throwable throwable) {
    this.thrown = throwable;
    // parses stack trace and loads classes
    this.extendedStackTrace = toExtendedStackTrace(stack, map, null, throwable.getStackTrace());
}
private static synchronized String generateName(boolean isConstructor, boolean forSerialization) {
    if (isConstructor) {
        int num = ++constructorSymnum;
        return "sun/reflect/GeneratedConstructorAccessor" + num;
    } else {
        int num = ++methodSymnum;
        return "sun/reflect/GeneratedMethodAccessor" + num;
    }
}
private static final EventTranslatorTwoArg<Log4jEventWrapper, LogEvent, AsyncLoggerConfig> MUTABLE_TRANSLATOR =
    new EventTranslatorTwoArg<Log4jEventWrapper, LogEvent, AsyncLoggerConfig>() {
        @Override
        public void translateTo(Log4jEventWrapper event, long sequence, LogEvent logEvent, AsyncLoggerConfig loggerConfig) {
            ((MutableLogEvent) event.event).initFrom(logEvent);
            event.loggerConfig = loggerConfig;
        }
    };

To mitigate these issues, the authors propose several strategies:

Replace the default AsyncAppender with a custom appender (e.g., AsyncScribeAppender) that builds the log snapshot without invoking ThrowableProxy.

Avoid using <AsyncLogger> tags in the XML configuration; instead use synchronous <Logger> elements or configure the async logger selector.

Disable the JVM’s reflection inflation by setting -Dsun.reflect.inflationThreshold=Integer.MAX_VALUE, at the cost of slower reflective calls.

Upgrade to JDK 9 or later where the Lambda class‑loading bug is fixed.

Configure the logging pattern to use %ex instead of the default %xEx to avoid loading extended stack‑trace information.

Set the async queue policy to DISCARD or implement a custom ErrorHandler that silently drops logs when the queue is full.

Finally, the article presents a recommended log4j2.xml configuration that follows these best practices, emphasizing explicit %ex usage, custom async appenders, and the removal of console appenders in production.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

JavaReflectionlogginglog4j2thread blockingasync appender
Meituan Technology Team
Written by

Meituan Technology Team

Over 10,000 engineers powering China’s leading lifestyle services e‑commerce platform. Supporting hundreds of millions of consumers, millions of merchants across 2,000+ industries. This is the public channel for the tech teams behind Meituan, Dianping, Meituan Waimai, Meituan Select, and related services.

0 followers
Reader feedback

How this landed with the community

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.