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;
+  }
 }

Reply via email to