Skip to content

Commit a4f1d32

Browse files
committed
Register application shutdown hook lazily
Previously, SpringApplicationShutdownHook would always register a shutdown hook, even if SpringApplication was configured not to use a shutdown hook, such as in a war deployment. This could result in a memory leak when the war was undeployed. The shutdown hook registered by SpringApplicationShutdownHook would remain registered, pinning the web application's class loader in memory. This commit updates SpringApplicationShutdownHook so that it registers a shutdown hook with the JVM lazily, upon registeration of the first application context. Fixes gh-27987
1 parent afb81f1 commit a4f1d32

File tree

2 files changed

+23
-14
lines changed

2 files changed

+23
-14
lines changed

spring-boot-project/spring-boot/src/main/java/org/springframework/boot/SpringApplicationShutdownHook.java

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import java.util.WeakHashMap;
2525
import java.util.concurrent.TimeUnit;
2626
import java.util.concurrent.TimeoutException;
27+
import java.util.concurrent.atomic.AtomicBoolean;
2728

2829
import org.apache.commons.logging.Log;
2930
import org.apache.commons.logging.LogFactory;
@@ -59,33 +60,38 @@ class SpringApplicationShutdownHook implements Runnable {
5960

6061
private final ApplicationContextClosedListener contextCloseListener = new ApplicationContextClosedListener();
6162

62-
private boolean inProgress;
63+
private final AtomicBoolean shutdownHookAdded = new AtomicBoolean(false);
6364

64-
SpringApplicationShutdownHook() {
65-
try {
66-
addRuntimeShutdownHook();
67-
}
68-
catch (AccessControlException ex) {
69-
// Not allowed in some environments
70-
}
71-
}
72-
73-
protected void addRuntimeShutdownHook() {
74-
Runtime.getRuntime().addShutdownHook(new Thread(this, "SpringApplicationShutdownHook"));
75-
}
65+
private boolean inProgress;
7666

7767
SpringApplicationShutdownHandlers getHandlers() {
7868
return this.handlers;
7969
}
8070

8171
void registerApplicationContext(ConfigurableApplicationContext context) {
72+
addRuntimeShutdownHookIfNecessary();
8273
synchronized (SpringApplicationShutdownHook.class) {
8374
assertNotInProgress();
8475
context.addApplicationListener(this.contextCloseListener);
8576
this.contexts.add(context);
8677
}
8778
}
8879

80+
private void addRuntimeShutdownHookIfNecessary() {
81+
if (this.shutdownHookAdded.compareAndSet(false, true)) {
82+
addRuntimeShutdownHook();
83+
}
84+
}
85+
86+
void addRuntimeShutdownHook() {
87+
try {
88+
Runtime.getRuntime().addShutdownHook(new Thread(this, "SpringApplicationShutdownHook"));
89+
}
90+
catch (AccessControlException ex) {
91+
// Not allowed in some environments
92+
}
93+
}
94+
8995
@Override
9096
public void run() {
9197
Set<ConfigurableApplicationContext> contexts;

spring-boot-project/spring-boot/src/test/java/org/springframework/boot/SpringApplicationShutdownHookTests.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,11 @@
4646
class SpringApplicationShutdownHookTests {
4747

4848
@Test
49-
void createCallsRegister() {
49+
void shutdownHookIsNotAddedUntilContextIsRegistered() {
5050
TestSpringApplicationShutdownHook shutdownHook = new TestSpringApplicationShutdownHook();
51+
assertThat(shutdownHook.isRuntimeShutdownHookAdded()).isFalse();
52+
ConfigurableApplicationContext context = new GenericApplicationContext();
53+
shutdownHook.registerApplicationContext(context);
5154
assertThat(shutdownHook.isRuntimeShutdownHookAdded()).isTrue();
5255
}
5356

0 commit comments

Comments
 (0)