Skip to content

Commit 41f3136

Browse files
committed
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.
1 parent 5e8fb31 commit 41f3136

File tree

1 file changed

+26
-2
lines changed

1 file changed

+26
-2
lines changed

src/main/java/org/scijava/Context.java

+26-2
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,13 @@
3333
import java.lang.reflect.Method;
3434
import java.net.URL;
3535
import java.net.URLClassLoader;
36+
import java.util.ArrayList;
3637
import java.util.Arrays;
3738
import java.util.Collection;
3839
import java.util.Collections;
3940
import java.util.List;
41+
import java.util.Map;
42+
import java.util.concurrent.ConcurrentHashMap;
4043

4144
import org.scijava.event.ContextCreatedEvent;
4245
import org.scijava.event.ContextDisposingEvent;
@@ -73,6 +76,14 @@ public class Context implements Disposable, AutoCloseable {
7376
*/
7477
public static final String STRICT_PROPERTY = "scijava.context.strict";
7578

79+
/** Set of currently active (not disposed) application contexts. */
80+
private static final Map<Context, Boolean> CONTEXTS =
81+
new ConcurrentHashMap<>(); // NB: ConcurrentHashMap disallows nulls.
82+
83+
// -- Static fields --
84+
85+
private static Thread shutdownThread = null;
86+
7687
// -- Fields --
7788

7889
/** Index of the application context's services. */
@@ -293,7 +304,20 @@ public Context(final Collection<Class<? extends Service>> serviceClasses,
293304
}
294305

295306
// If JVM shuts down with context still active, clean up after ourselves.
296-
Runtime.getRuntime().addShutdownHook(new Thread(() -> doDispose(false)));
307+
if (shutdownThread == null) {
308+
synchronized (Context.class) {
309+
if (shutdownThread == null) {
310+
shutdownThread = new Thread(() -> {
311+
final List<Context> contexts = new ArrayList<>(CONTEXTS.keySet());
312+
for (final Context context : contexts) {
313+
context.doDispose(false);
314+
}
315+
});
316+
Runtime.getRuntime().addShutdownHook(shutdownThread);
317+
}
318+
}
319+
}
320+
CONTEXTS.put(this, true);
297321

298322
// Publish an event to indicate that context initialization is complete.
299323
final EventService eventService = getService(EventService.class);
@@ -432,7 +456,6 @@ public boolean isInjectable(final Class<?> type) {
432456

433457
@Override
434458
public void dispose() {
435-
if (disposed) return;
436459
doDispose(true);
437460
}
438461

@@ -589,6 +612,7 @@ private String createMissingServiceMessage(
589612
private synchronized void doDispose(final boolean announce) {
590613
if (disposed) return;
591614
disposed = true;
615+
CONTEXTS.remove(this);
592616
if (announce) {
593617
final EventService eventService = getService(EventService.class);
594618
if (eventService != null) eventService.publish(new ContextDisposingEvent());

0 commit comments

Comments
 (0)