Skip to content

Potential Memory Leak: Unbounded StringBuilder retention via ThreadLocal in EcsJsonSerializer #381

@QiuYucheng2003

Description

@QiuYucheng2003

Describe the bug

There is a potential memory leak (Heap Exhaustion via Large Object Retention) caused by the improper reset of a ThreadLocal cached StringBuilder in co.elastic.logging.EcsJsonSerializer.

Root Cause

In EcsJsonSerializer.java, a ThreadLocal<StringBuilder> named messageStringBuilder is used to reuse StringBuilder instances to avoid allocations.

When retrieving the builder via getMessageStringBuilder(), the code calls result.setLength(0) to reset it. However, while setLength(0) resets the logical length, it does not shrink the underlying char[] capacity.

Impact

If an exceptionally large log message or a very deep exception stack trace (e.g., via formatThrowable) is serialized, the StringBuilder will expand its capacity significantly. In a typical thread-pool environment (e.g., Web container worker threads), these bloated StringBuilder objects will remain bound to long-living core threads permanently. Over time, this causes linear heap memory inflation and can eventually lead to an OutOfMemoryError (OOM).

Suggested Fix

Introduce a maximum capacity threshold when acquiring or releasing the StringBuilder. If the current capacity exceeds the threshold (e.g., 4096), discard the oversized builder and replace it with a newly allocated one.

Example fix in getMessageStringBuilder():

public static StringBuilder getMessageStringBuilder() {
    StringBuilder result = messageStringBuilder.get();
    if (result == null || result.capacity() > 4096) { // Add capacity check
        result = new StringBuilder(1024);
        messageStringBuilder.set(result);
    } else {
        result.setLength(0);
    }
    return result;
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    agent-javacommunityIssues and PRs created by the communitytriageIssues and PRs that need to be triaged

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions