This is an automated email from the ASF dual-hosted git repository. lukaszlenart pushed a commit to branch WW-5537-classloader-leak-fixes in repository https://gitbox.apache.org/repos/asf/struts.git
commit 3cb57c365ee2d76cb66f4acd0622fda50471932a Author: Lukasz Lenart <[email protected]> AuthorDate: Mon Mar 23 09:55:18 2026 +0100 WW-5537 FinalizableReferenceQueue: volatile instance, join, classloader null Co-Authored-By: Claude Opus 4.6 <[email protected]> --- .../inject/util/FinalizableReferenceQueue.java | 43 ++++++++++++++++++++-- 1 file changed, 39 insertions(+), 4 deletions(-) diff --git a/core/src/main/java/org/apache/struts2/inject/util/FinalizableReferenceQueue.java b/core/src/main/java/org/apache/struts2/inject/util/FinalizableReferenceQueue.java index 6556000f7..777a00722 100644 --- a/core/src/main/java/org/apache/struts2/inject/util/FinalizableReferenceQueue.java +++ b/core/src/main/java/org/apache/struts2/inject/util/FinalizableReferenceQueue.java @@ -18,6 +18,7 @@ package org.apache.struts2.inject.util; import java.lang.ref.Reference; import java.lang.ref.ReferenceQueue; +import java.util.concurrent.atomic.AtomicReference; import java.util.logging.Level; import java.util.logging.Logger; @@ -26,11 +27,13 @@ import java.util.logging.Logger; * * @author Bob Lee ([email protected]) */ -class FinalizableReferenceQueue extends ReferenceQueue<Object> { +public class FinalizableReferenceQueue extends ReferenceQueue<Object> { private static final Logger logger = Logger.getLogger(FinalizableReferenceQueue.class.getName()); + private final AtomicReference<Thread> cleanupThread = new AtomicReference<>(); + private FinalizableReferenceQueue() {} void cleanUp(Reference reference) { @@ -49,18 +52,38 @@ class FinalizableReferenceQueue extends ReferenceQueue<Object> { Thread thread = new Thread("FinalizableReferenceQueue") { @Override public void run() { - while (true) { + while (!Thread.currentThread().isInterrupted()) { try { cleanUp(remove()); - } catch (InterruptedException e) { /* ignore */ } + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + break; + } } } }; thread.setDaemon(true); thread.start(); + cleanupThread.set(thread); + } + + /** + * Stops the background cleanup thread to prevent classloader memory leaks during hot redeployment. + */ + void stop() { + Thread t = cleanupThread.getAndSet(null); + if (t != null) { + t.interrupt(); + try { + t.join(5000); + } catch (InterruptedException ignored) { + Thread.currentThread().interrupt(); + } + t.setContextClassLoader(null); + } } - static ReferenceQueue<Object> instance = createAndStart(); + static volatile ReferenceQueue<Object> instance = createAndStart(); static FinalizableReferenceQueue createAndStart() { FinalizableReferenceQueue queue = new FinalizableReferenceQueue(); @@ -74,4 +97,16 @@ class FinalizableReferenceQueue extends ReferenceQueue<Object> { public static ReferenceQueue<Object> getInstance() { return instance; } + + /** + * Stops the cleanup thread and clears the instance to prevent classloader + * memory leaks during hot redeployment. + */ + public static void stopAndClear() { + ReferenceQueue<Object> q = instance; + if (q instanceof FinalizableReferenceQueue frq) { + frq.stop(); + } + instance = null; + } }
