[ https://issues.apache.org/jira/browse/CXF-9146?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=17973483#comment-17973483 ]
Eric commented on CXF-9146: --------------------------- After experiencing Crashes in our production environments because of OutOfMemoryErrors caused by the MemoryLeaks by this issue{*}, I can attest that an upping this ticket to CRITICAL is still necessary in my opinion.{*}{*}{*} Also I have recently learned the hard way that the MemoryLeak in the ThreadLocalClientState ist only the JAXRS-Side of the error. A very similiar issue exists in {color:#000000}{color:#000000}org.apache.cxf.endpoint.{color}{color}{color:#000000}ClientImpl{color} which also hosts a WeakHashMap of Threads. I will see that I provide Fixes for these as well. > MemoryLeak caused by ThreadLocalClientState when used with the > HttpClientConduit > -------------------------------------------------------------------------------- > > Key: CXF-9146 > URL: https://issues.apache.org/jira/browse/CXF-9146 > Project: CXF > Issue Type: Bug > Reporter: Eric > Priority: Major > 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)