Skip to content

Commit 6df88cc

Browse files
committed
8389096
Hi all, please review this fix to avoid deadlocks (or crashes in earlier VM versions) when we want to start GCs with `GCALotAtAllSafepoints` (and `ScavengeALot`). There are actually two causes: * GCs that use the GCLocker do not allow starting GCs when it is held * the other issue is the GC start when holding the `Heap_lock`; starting a new GC will automatically try to grab it (recursively), which HotSpot does not support, resulting in a hagn too The change * does not start the GC-a-lot procedure when we are in a critical section (`JavaThread::in_critical()` is only set for collectors using it, so others will just correctly advance) * when waiting for the `Heap_lock` the VM also suppresses (only) the GC invocation. This is only the case for the `JVM_WaitForReferencePendingList` call. Arguably we may want to disable GC when waiting for any lock. This is up for discussion, but it works with just suppressing them with the `Heap_lock` held. Testing: test case does not hang any more. It's not a 100% reproducer, but something like 75%. I opted for simplicity than reproducability here. Thanks, Thomas
1 parent b820734 commit 6df88cc

6 files changed

Lines changed: 127 additions & 11 deletions

File tree

src/hotspot/share/runtime/interfaceSupport.cpp

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,10 +85,14 @@ unsigned int InterfaceSupport::_fullgc_alot_counter = 1;
8585
intx InterfaceSupport::_fullgc_alot_invocation = 0;
8686

8787
void InterfaceSupport::gc_alot() {
88-
Thread *thread = Thread::current();
88+
Thread* thread = Thread::current();
8989
if (!thread->is_Java_thread()) return; // Avoid concurrent calls
90+
JavaThread* current_thread = JavaThread::cast(thread);
91+
// If we are in a critical section, collectors deadlock when trying to start a GC.
92+
// In_critical() is only set for those collectors where this is the case. Others
93+
// like G1 do not and have no problem garbage collecting here.
94+
if (current_thread->in_critical()) return;
9095
// Check for new, not quite initialized thread. A thread in new mode cannot initiate a GC.
91-
JavaThread *current_thread = JavaThread::cast(thread);
9296
if (current_thread->active_handles() == nullptr) return;
9397

9498
// Short-circuit any possible re-entrant gc-a-lot attempt

src/hotspot/share/runtime/javaThread.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -281,7 +281,7 @@ void JavaThread::check_possible_safepoint() {
281281
#endif // CHECK_UNHANDLED_OOPS
282282
}
283283

284-
void JavaThread::check_for_valid_safepoint_state() {
284+
void JavaThread::check_for_valid_safepoint_state(bool allow_gcalot) {
285285
// Don't complain if running a debugging command.
286286
if (DebuggingContext::is_enabled()) return;
287287

@@ -294,7 +294,7 @@ void JavaThread::check_for_valid_safepoint_state() {
294294
fatal("LEAF method calling lock?");
295295
}
296296

297-
if (GCALotAtAllSafepoints) {
297+
if (GCALotAtAllSafepoints && allow_gcalot) {
298298
// We could enter a safepoint here and thus have a gc
299299
InterfaceSupport::check_gc_alot();
300300
}

src/hotspot/share/runtime/javaThread.hpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -284,8 +284,8 @@ class JavaThread: public Thread {
284284
public:
285285
// These functions check conditions before possibly going to a safepoint.
286286
// including NoSafepointVerifier.
287-
void check_for_valid_safepoint_state() NOT_DEBUG_RETURN;
288-
void check_possible_safepoint() NOT_DEBUG_RETURN;
287+
void check_for_valid_safepoint_state(bool allow_gcalot = true) NOT_DEBUG_RETURN;
288+
void check_possible_safepoint() NOT_DEBUG_RETURN;
289289

290290
#ifdef ASSERT
291291
private:

src/hotspot/share/runtime/mutex.cpp

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ void Mutex::check_block_state(Thread* thread) {
6161
"locking not allowed when crash protection is set");
6262
}
6363

64-
void Mutex::check_safepoint_state(Thread* thread) {
64+
void Mutex::check_safepoint_state(Thread* thread, bool allow_gcalot) {
6565
check_block_state(thread);
6666

6767
// If the lock acquisition checks for safepoint, verify that the lock was created with rank that
@@ -72,7 +72,7 @@ void Mutex::check_safepoint_state(Thread* thread) {
7272

7373
if (thread->is_active_Java_thread()) {
7474
// Also check NoSafepointVerifier, and thread state is _thread_in_vm
75-
JavaThread::cast(thread)->check_for_valid_safepoint_state();
75+
JavaThread::cast(thread)->check_for_valid_safepoint_state(allow_gcalot);
7676
}
7777
}
7878

@@ -116,7 +116,7 @@ void Mutex::lock_contended(Thread* self) {
116116
void Mutex::lock(Thread* self) {
117117
assert(owner() != self, "invariant");
118118

119-
check_safepoint_state(self);
119+
check_safepoint_state(self, true /* allow_gcalot */);
120120
check_rank(self);
121121

122122
OrderAccess::fence();
@@ -245,7 +245,9 @@ bool Monitor::wait(uint64_t timeout) {
245245
set_owner(nullptr);
246246

247247
// Check safepoint state after resetting owner and possible NSV.
248-
check_safepoint_state(self);
248+
// Don't do GCALot verification here: the lock is held until the wait() below, and if
249+
// this lock is Heap_lock, we would deadlock.
250+
check_safepoint_state(self, this != Heap_lock);
249251

250252
int wait_status;
251253
InFlightMutexRelease ifmr(this);

src/hotspot/share/runtime/mutex.hpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ class Mutex : public CHeapObj<mtSynchronizer> {
141141
protected:
142142
void set_owner_implementation(Thread* owner) NOT_DEBUG({ raw_set_owner(owner);});
143143
void check_block_state (Thread* thread) NOT_DEBUG_RETURN;
144-
void check_safepoint_state (Thread* thread) NOT_DEBUG_RETURN;
144+
void check_safepoint_state (Thread* thread, bool allow_gcalot) NOT_DEBUG_RETURN;
145145
void check_no_safepoint_state(Thread* thread) NOT_DEBUG_RETURN;
146146
void check_rank (Thread* thread) NOT_DEBUG_RETURN;
147147
void assert_owner (Thread* expected) NOT_DEBUG_RETURN;
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
/*
2+
* Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* This code is free software; you can redistribute it and/or modify it
6+
* under the terms of the GNU General Public License version 2 only, as
7+
* published by the Free Software Foundation.
8+
*
9+
* This code is distributed in the hope that it will be useful, but WITHOUT
10+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
12+
* version 2 for more details (a copy is included in the LICENSE file that
13+
* accompanied this code).
14+
*
15+
* You should have received a copy of the GNU General Public License version
16+
* 2 along with this work; if not, write to the Free Software Foundation,
17+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18+
*
19+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20+
* or visit www.oracle.com if you need additional information or have any
21+
* questions.
22+
*/
23+
24+
package gc;
25+
26+
/**
27+
* @test id=Serial
28+
* @bug 8389096
29+
* @summary Verify that -XX:GCALotAtAllSafepoints and -XX:+ScavengeALot do not hang the VM.
30+
* @comment GCALotAtAllSafepoints and ScavengeALot cause garbage collections at many places in the VM. These
31+
* garbage collection should not cause hangs.
32+
* @requires vm.flagless
33+
* @requires vm.debug
34+
* @requires vm.gc.Serial
35+
* @modules java.base/jdk.internal.misc
36+
* @library /test/lib /
37+
* @run driver/timeout=60 gc.TestGCALotAtAllSafepoints -XX:+UseSerialGC
38+
*/
39+
40+
/**
41+
* @test id=Parallel
42+
* @bug 8389096
43+
* @summary Verify that -XX:GCALotAtAllSafepoints and -XX:+ScavengeALot do not hang the VM.
44+
* @comment GCALotAtAllSafepoints and ScavengeALot cause garbage collections at many places in the VM. These
45+
* garbage collection should not cause hangs.
46+
* @requires vm.flagless
47+
* @requires vm.debug
48+
* @requires vm.gc.Parallel
49+
* @modules java.base/jdk.internal.misc
50+
* @library /test/lib /
51+
* @run driver/timeout=60 gc.TestGCALotAtAllSafepoints -XX:+UseParallelGC
52+
*/
53+
54+
/**
55+
* @test id=G1
56+
* @bug 8389096
57+
* @summary Verify that -XX:GCALotAtAllSafepoints and -XX:+ScavengeALot do not hang the VM.
58+
* @comment GCALotAtAllSafepoints and ScavengeALot cause garbage collections at many places in the VM. These
59+
* garbage collection should not cause hangs.
60+
* @requires vm.flagless
61+
* @requires vm.debug
62+
* @requires vm.gc.G1
63+
* @modules java.base/jdk.internal.misc
64+
* @library /test/lib /
65+
* @run driver/timeout=60 gc.TestGCALotAtAllSafepoints -XX:+UseG1GC
66+
*/
67+
68+
/**
69+
* @test id=Z
70+
* @bug 8389096
71+
* @summary Verify that -XX:GCALotAtAllSafepoints and -XX:+ScavengeALot do not hang the VM.
72+
* @comment GCALotAtAllSafepoints and ScavengeALot cause garbage collections at many places in the VM. These
73+
* garbage collection should not cause hangs.
74+
* @requires vm.flagless
75+
* @requires vm.debug
76+
* @requires vm.gc.Z
77+
* @modules java.base/jdk.internal.misc
78+
* @library /test/lib /
79+
* @run driver/timeout=60 gc.TestGCALotAtAllSafepoints -XX:+UseZGC
80+
*/
81+
82+
/**
83+
* @test id=Shenandoah
84+
* @bug 8389096
85+
* @summary Verify that -XX:GCALotAtAllSafepoints and -XX:+ScavengeALot do not hang the VM.
86+
* @comment GCALotAtAllSafepoints and ScavengeALot cause garbage collections at many places in the VM. These
87+
* garbage collection should not cause hangs.
88+
* @requires vm.flagless
89+
* @requires vm.debug
90+
* @requires vm.gc.Shenandoah
91+
* @modules java.base/jdk.internal.misc
92+
* @library /test/lib /
93+
* @run driver/timeout=60 gc.TestGCALotAtAllSafepoints -XX:+UseShenandoahGC
94+
*/
95+
96+
import jdk.test.lib.process.ProcessTools;
97+
import jdk.test.lib.process.OutputAnalyzer;
98+
99+
public class TestGCALotAtAllSafepoints {
100+
public static void main(String[] args) throws Exception {
101+
ProcessBuilder pb = ProcessTools.createLimitedTestJavaProcessBuilder(args[0],
102+
"-Xmx16m",
103+
"-XX:+GCALotAtAllSafepoints",
104+
"-XX:+ScavengeALot",
105+
"NoSuchClass");
106+
OutputAnalyzer output = new OutputAnalyzer(pb.start());
107+
output.shouldMatch("Error: Could not find or load main class NoSuchClass");
108+
output.shouldHaveExitValue(1);
109+
}
110+
}

0 commit comments

Comments
 (0)