[ 
https://issues.apache.org/jira/browse/CXF-9146?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
 ]

Andriy Redko updated CXF-9146:
------------------------------
    Fix Version/s: 4.1.3
                   4.0.9
                   3.6.8

> MemoryLeak in HttpClientHTTPConduit when used with the ThreadLocalClientState
> -----------------------------------------------------------------------------
>
>                 Key: CXF-9146
>                 URL: https://issues.apache.org/jira/browse/CXF-9146
>             Project: CXF
>          Issue Type: Bug
>    Affects Versions: 4.1.2, 4.0.8, 3.6.7
>            Reporter: Eric
>            Priority: Major
>             Fix For: 4.1.3, 4.0.9, 3.6.8
>
>         Attachments: CxfClientMemoryLeakTest.java, 
> image-2025-06-10-19-04-43-021.png, image-2025-06-10-19-05-03-593.png
>
>
> {color:#000000}Is defined as following:{color}
> {code:java}
> public class ThreadLocalClientState implements ClientState {
>        private Map<Thread, LocalClientState> state = 
> Collections.synchronizedMap(new WeakHashMap<Thread, LocalClientState>()); 
> {code}
> ... which does not make any sense to me, as is this is just ThreadLocal built 
> at home, a class which as been in the JDK for a very long time.. A 
> WeakHashMap of Threads might emulate ThreadLocal,  but it can cause 
> unexpected memory leaks, because the Weak-Keys are strongly held until the 
> WeakHashMap is accessed for the next time.
>  
> Let's take this simple example:
> {code:java}
> var mb = 1024 * 1024;
> var rt = Runtime.getRuntime();
> var entries = new WeakHashMap<Object, byte[][]>();
> var keys = new ArrayList<>();
> System.gc();
> System.out.printf("Memory before Memory Load: %s mb%n", (rt.totalMemory() - 
> rt.freeMemory()) / mb);
> IntStream.range(0, 100).mapToObj(i -> new Date(i + 1000)).forEach(key -> {
>     keys.add(key);
>     entries.put(key, new byte[1][10 * mb]);
> });
> System.gc();
> System.out.printf("Memory after Memory Load: %s mb%n", (rt.totalMemory() - 
> rt.freeMemory()) / mb);
> keys.clear();
> System.gc();
> System.out.printf("Memory after Memory Keys clear: %s mb%n", 
> (rt.totalMemory() - rt.freeMemory()) / mb);
> System.out.printf("Map Size: %s%n", entries.size());
> System.gc();
> System.out.printf("Memory after first Memory Map access: %s mb%n", 
> (rt.totalMemory() - rt.freeMemory()) / mb);{code}
>  
> This leads to the following output:
> {noformat}
> Memory before Memory Load: 13 mb
> Memory after Memory Load: 1213 mb
> Memory after Memory Keys clear: 1213 mb
> Map Size: 0
> Memory after first Memory Map access: 13 mb
> {noformat}
>  
> Please ignore my inaccurate measurements with System.gc() without using JMH, 
> but when looking up the source for WeakHashMap then it becomes obvious that 
> no cleanup is technically possible until the WeakHashMap is deferenced for 
> the next time for a method of the Map interface. This is a scenario which 
> should rarely happen in a production enviroment, but we have seen it at least 
> once being the cause of an OutOfMemoryError wenn many threads in in parallel 
> executed requests which returned very large Response objects in memory.
> Therefore, and especially when thinking of the future of virtual threads and 
> scoped values, I would advise to the refactor the code so that it internally 
> uses a simple Treadlocal from now on instead of the WeakHashMap.



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

Reply via email to