Skip to content

Commit 2a7c7bf

Browse files
chrisdennisshipilev
authored andcommitted
8362123: ClassLoader Leak via Executors.newSingleThreadExecutor(...)
Backport-of: d5a207994b9c374e6638e57826326f8f4593b96b
1 parent 985c61e commit 2a7c7bf

File tree

2 files changed

+64
-2
lines changed

2 files changed

+64
-2
lines changed

src/java.base/share/classes/java/util/concurrent/Executors.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -846,6 +846,13 @@ public void shutdown() {
846846
super.shutdown();
847847
cleanable.clean(); // unregisters the cleanable
848848
}
849+
850+
@Override
851+
public List<Runnable> shutdownNow() {
852+
List<Runnable> unexecuted = super.shutdownNow();
853+
cleanable.clean(); // unregisters the cleanable
854+
return unexecuted;
855+
}
849856
}
850857

851858
/**

test/jdk/java/util/concurrent/Executors/AutoShutdown.java

Lines changed: 57 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,23 +23,32 @@
2323

2424
/*
2525
* @test
26-
* @bug 6399443 8302899
26+
* @bug 6399443 8302899 8362123
2727
* @summary Test that Executors.newSingleThreadExecutor wraps an ExecutorService that
2828
* automatically shuts down and terminates when the wrapper is GC'ed
29+
* @library /test/lib/
2930
* @modules java.base/java.util.concurrent:+open
3031
* @run junit AutoShutdown
3132
*/
3233

34+
import java.lang.ref.PhantomReference;
35+
import java.lang.ref.Reference;
36+
import java.lang.ref.ReferenceQueue;
3337
import java.lang.reflect.Field;
3438
import java.time.Duration;
3539
import java.util.concurrent.ExecutorService;
3640
import java.util.concurrent.Executors;
41+
import java.util.concurrent.ThreadFactory;
3742
import java.util.concurrent.TimeUnit;
3843
import java.util.concurrent.atomic.AtomicInteger;
44+
import java.util.function.Consumer;
3945
import java.util.function.Supplier;
4046
import java.util.stream.Stream;
4147
import java.util.stream.IntStream;
4248

49+
import jdk.test.lib.Utils;
50+
import jdk.test.lib.util.ForceGC;
51+
4352
import org.junit.jupiter.api.Test;
4453
import org.junit.jupiter.params.ParameterizedTest;
4554
import org.junit.jupiter.params.provider.MethodSource;
@@ -61,6 +70,13 @@ private static Stream<Arguments> executorAndQueuedTaskCounts() {
6170
.mapToObj(i -> Arguments.of(s, i)));
6271
}
6372

73+
private static Stream<Arguments> shutdownMethods() {
74+
return Stream.<Consumer<ExecutorService>>of(
75+
e -> e.shutdown(),
76+
e -> e.shutdownNow()
77+
).map(Arguments::of);
78+
}
79+
6480
/**
6581
* SingleThreadExecutor with no worker threads.
6682
*/
@@ -110,6 +126,28 @@ void testActiveWorker(Supplier<ExecutorService> supplier,int queuedTaskCount) th
110126
assertEquals(ntasks, completedTaskCount.get());
111127
}
112128

129+
@ParameterizedTest
130+
@MethodSource("shutdownMethods")
131+
void testShutdownUnlinksCleaner(Consumer<ExecutorService> shutdown) throws Exception {
132+
ClassLoader classLoader =
133+
Utils.getTestClassPathURLClassLoader(ClassLoader.getPlatformClassLoader());
134+
135+
ReferenceQueue<?> queue = new ReferenceQueue<>();
136+
Reference<?> reference = new PhantomReference(classLoader, queue);
137+
try {
138+
Class<?> isolatedClass = classLoader.loadClass("AutoShutdown$IsolatedClass");
139+
assertSame(isolatedClass.getClassLoader(), classLoader);
140+
isolatedClass.getDeclaredMethod("shutdown", Consumer.class).invoke(null, shutdown);
141+
142+
isolatedClass = null;
143+
classLoader = null;
144+
145+
assertTrue(ForceGC.wait(() -> queue.poll() != null));
146+
} finally {
147+
Reference.reachabilityFence(reference);
148+
}
149+
}
150+
113151
/**
114152
* Returns the delegate for the given ExecutorService. The given ExecutorService
115153
* must be a Executors$DelegatedExecutorService.
@@ -132,5 +170,22 @@ private void gcAndAwaitTermination(ExecutorService executor) throws Exception {
132170
terminated = executor.awaitTermination(100, TimeUnit.MILLISECONDS);
133171
}
134172
}
135-
}
136173

174+
public static class IsolatedClass {
175+
176+
private static final ExecutorService executor =
177+
Executors.newSingleThreadExecutor(new IsolatedThreadFactory());
178+
179+
public static void shutdown(Consumer<ExecutorService> shutdown) {
180+
shutdown.accept(executor);
181+
}
182+
}
183+
184+
public static class IsolatedThreadFactory implements ThreadFactory {
185+
186+
@Override
187+
public Thread newThread(Runnable r) {
188+
return new Thread(r);
189+
}
190+
}
191+
}

0 commit comments

Comments
 (0)