Great! Thanks for creating the ticket Raju, and thanks for the fix Gary.

________________________________________
From: Raju Gupta <[email protected]>
Sent: 31 October 2026 00:45
To: Commons Users List
Cc: Gillespie, Oli
Subject: RE: [EXTERNAL] Memory leak in ReflectionToStringBuilder/ToStringStyle

CAUTION: This email originated from outside of the organization. Do not click 
links or open attachments unless you can confirm the sender and know the 
content is safe.

Hi Gary, Oli,

I've filed a Jira ticket for this and opened a pull request with a fix.

Jira: https://issues.apache.org/jira/browse/LANG-1839
PR:   https://github.com/apache/commons-lang/pull/1846

I reproduced the leak on current master using Oli's reproducer (VISITED grew to 
1,000,000 entries after 1,000 invocations, and an OutOfMemoryError followed 
with a small heap).

Cause: a ReflectionToStringBuilder registers its root object in the 
constructor, but only toString() unregisters it. A builder that is created and 
never stringified leaves a stale entry in REGISTRY, so REGISTRY never becomes 
empty. Since VISITED is only cleared when REGISTRY is empty, it is never 
cleared and grows on every later toString() with RecursiveToStringStyle.

Fix: clear VISITED in a finally block when the outermost 
ReflectionToStringBuilder.toString() completes, instead of relying on REGISTRY 
being empty. The PR includes regression tests, and the builder package tests 
and checkstyle pass locally.

I did not change the older, small leak of one REGISTRY entry per orphaned 
builder, to keep the change minimal. Happy to look at that separately if you 
think it's worth it.

Oli, thank you for the report and the reproducer.

Thanks,
Raju


On Fri, Oct 30, 2026 at 7:31 PM Gary Gregory 
<[email protected]<mailto:[email protected]>> wrote:
Hi Oli,

Anyone can apply for an Apache Jira account.

Gary

On Fri, Oct 30, 2026, 05:55 Gillespie, Oli 
<[email protected]<mailto:[email protected]>> wrote:

> Thanks. No I don't have a suggested fix, and I don't think I have an
> account for Apache Commons Jira so I figured I'd at least report it here.
>
> Oli
>
> ________________________________________
> From: Gary Gregory <[email protected]<mailto:[email protected]>>
> Sent: 29 October 2026 22:13
> To: Commons Users List
> Cc: Gillespie, Oli
> Subject: RE: [EXTERNAL] Memory leak in
> ReflectionToStringBuilder/ToStringStyle
>
> CAUTION: This email originated from outside of the organization. Do not
> click links or open attachments unless you can confirm the sender and know
> the content is safe.
>
> Hello Oli,
>
> I apologize for the bad surprise on the migration. Do you have a
> suggestion? If you have time, feel free to offer it on GitHub as a PR.
> Otherwise, please create a Jira ticket so we can track this.
>
> Thank you,
> Gary
>
> On Thu, Oct 29, 2026, 18:06 Gillespie, Oli via user <
> [email protected]<mailto:[email protected]><mailto:[email protected]<mailto:[email protected]>>>
>  wrote:
> Hi,
>
> After upgrading to 3.21.0, I saw a memory leak in my application related
> to the changes in ToStringStyle. The new 'VISITED'[1] ThreadLocal
> IdentityHashMap shows unbounded growth when used in this (admittedly
> strange) fashion.
>
> Here's a reproducer:
>
> ```
> /*
>  * curl -fLO
> https://repo1.maven.org/maven2/org/apache/commons/commons-lang3/3.21.0/commons-lang3-3.21.0.jar
>  * java -cp commons-lang3-3.21.0.jar VisitedLeak.java // leaks
>  * // Invocation 1000: REGISTRY.size=1 VISITED.size=1000000
>  * java -cp commons-lang3-3.21.0.jar VisitedLeak.java clean // control, no
> leak
>  */
> import org.apache.commons.lang3.builder.*;
> import java.lang.reflect.Field;
> import java.util.*;
>
> public class VisitedLeak {
>     static class Item { int id; }
>     static class Graph { List<Object> items = new ArrayList<>() {{ for
> (int i = 0; i < 999; i++) add(new Object()); }}; }
>
>     static int size(String field) throws Exception {
>         Field f = ToStringStyle.class.getDeclaredField(field);
> f.setAccessible(true);
>         return ((Map<?, ?>) ((ThreadLocal<?>) f.get(null)).get()).size();
>     }
>
>     public static void main(String[] args) throws Exception {
>         if (args.length == 0) new ReflectionToStringBuilder(new Graph(),
> new RecursiveToStringStyle()); // start leak
>         for (int i = 0; i <= 1000; i++) {
>             if (i % 100 == 0) System.out.printf("Invocation %d:
> REGISTRY.size=%d VISITED.size=%d%n", i, size("REGISTRY"), size("VISITED"));
>             new ReflectionToStringBuilder(new Graph(), new
> RecursiveToStringStyle()).toString();
>         }
>     }
> }
> ```
> prints `Invocation 1000: REGISTRY.size=1 VISITED.size=1000000`,
> demonstrating the leak.
>
> In my application this was because of some awkward logging code:
> `log.debug("...", new ReflectionToStringBuilder(...))`. toString() is not
> called if debug logging is not enabled, starting the leak. Easy fix on my
> side, but reporting in case you consider this a bug, or for anyone else
> that might run into it.
>
> Oli
>
> [1]
> https://github.com/apache/commons-lang/blob/master/src/main/java/org/apache/commons/lang3/builder/ToStringStyle.java#L607
>
>
>
> Amazon Development Centre (London) Ltd. Registered in England and Wales
> with registration number 04543232 with its registered office at 1
> Principal Place
> <https://www.google.com/maps/search/1+Principal+Place?entry=gmail&source=g>
> <https://www.google.com/maps/search/1+Principal+Place?entry=gmail&source=g>,
> Worship Street, London EC2A 2FA, United Kingdom.
>
>
>
>
> ---------------------------------------------------------------------
> To unsubscribe, e-mail: 
> [email protected]<mailto:[email protected]><mailto:
> [email protected]<mailto:[email protected]>>
> For additional 
> c<https://www.google.com/maps/search/%3E+For+additional+c?entry=gmail&source=g>ommands,
>  e-mail: 
> [email protected]<mailto:[email protected]><mailto:
> [email protected]<mailto:[email protected]>>
>
>
>
>
> Amazon Development Centre (London) Ltd. Registered in England and Wales
> with registration number 04543232 with its registered office at 1
> Principal Place
> <https://www.google.com/maps/search/1+Principal+Place?entry=gmail&source=g>,
> Worship Street, London EC2A 2FA, United Kingdom.
>
>
>
>



Amazon Development Centre (London) Ltd. Registered in England and Wales with 
registration number 04543232 with its registered office at 1 Principal Place, 
Worship Street, London EC2A 2FA, United Kingdom.


Reply via email to