From 41f31366618fb6ab6ba5e612e61b5c9755d8dd43 Mon Sep 17 00:00:00 2001 From: Curtis Rueden Date: Thu, 19 Oct 2023 10:06:48 +0200 Subject: [PATCH] Only register one single shutdown hook Registering one shutdown hook Thread object per Context ever created eventually causes the JVM to start throwing around OutOfMemoryErrors. --- src/main/java/org/scijava/Context.java | 28 ++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/scijava/Context.java b/src/main/java/org/scijava/Context.java index d6b777d46..421ad2b36 100644 --- a/src/main/java/org/scijava/Context.java +++ b/src/main/java/org/scijava/Context.java @@ -33,10 +33,13 @@ import java.lang.reflect.Method; import java.net.URL; import java.net.URLClassLoader; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; import org.scijava.event.ContextCreatedEvent; import org.scijava.event.ContextDisposingEvent; @@ -73,6 +76,14 @@ public class Context implements Disposable, AutoCloseable { */ public static final String STRICT_PROPERTY = "scijava.context.strict"; + /** Set of currently active (not disposed) application contexts. */ + private static final Map CONTEXTS = + new ConcurrentHashMap<>(); // NB: ConcurrentHashMap disallows nulls. + + // -- Static fields -- + + private static Thread shutdownThread = null; + // -- Fields -- /** Index of the application context's services. */ @@ -293,7 +304,20 @@ public Context(final Collection> serviceClasses, } // If JVM shuts down with context still active, clean up after ourselves. - Runtime.getRuntime().addShutdownHook(new Thread(() -> doDispose(false))); + if (shutdownThread == null) { + synchronized (Context.class) { + if (shutdownThread == null) { + shutdownThread = new Thread(() -> { + final List contexts = new ArrayList<>(CONTEXTS.keySet()); + for (final Context context : contexts) { + context.doDispose(false); + } + }); + Runtime.getRuntime().addShutdownHook(shutdownThread); + } + } + } + CONTEXTS.put(this, true); // Publish an event to indicate that context initialization is complete. final EventService eventService = getService(EventService.class); @@ -432,7 +456,6 @@ public boolean isInjectable(final Class type) { @Override public void dispose() { - if (disposed) return; doDispose(true); } @@ -589,6 +612,7 @@ private String createMissingServiceMessage( private synchronized void doDispose(final boolean announce) { if (disposed) return; disposed = true; + CONTEXTS.remove(this); if (announce) { final EventService eventService = getService(EventService.class); if (eventService != null) eventService.publish(new ContextDisposingEvent());