Skip to content

Commit 0d14636

Browse files
committedNov 30, 2023
8320515: assert(monitor->object_peek() != nullptr) failed: Owned monitors should not have a dead object
Reviewed-by: dholmes, ihse, sspitsyn, dcubed
1 parent d6b4aa0 commit 0d14636

File tree

8 files changed

+325
-7
lines changed

8 files changed

+325
-7
lines changed
 

‎make/test/JtregNativeHotspot.gmk

+2-1
Original file line numberDiff line numberDiff line change
@@ -861,7 +861,7 @@ BUILD_HOTSPOT_JTREG_EXECUTABLES_LIBS_exesigtest := -ljvm
861861

862862
ifeq ($(call isTargetOs, windows), true)
863863
BUILD_HOTSPOT_JTREG_EXECUTABLES_CFLAGS_exeFPRegs := -MT
864-
BUILD_HOTSPOT_JTREG_EXCLUDE += exesigtest.c libterminatedThread.c libTestJNI.c libCompleteExit.c libTestPsig.c exeGetCreatedJavaVMs.c
864+
BUILD_HOTSPOT_JTREG_EXCLUDE += exesigtest.c libterminatedThread.c libTestJNI.c libCompleteExit.c libMonitorWithDeadObjectTest.c libTestPsig.c exeGetCreatedJavaVMs.c
865865
BUILD_HOTSPOT_JTREG_LIBRARIES_LIBS_libatExit := jvm.lib
866866
BUILD_HOTSPOT_JTREG_LIBRARIES_LIBS_libnativeStack := jvm.lib
867867
BUILD_HOTSPOT_JTREG_EXECUTABLES_LIBS_exedaemonDestroy := jvm.lib
@@ -1503,6 +1503,7 @@ else
15031503
BUILD_HOTSPOT_JTREG_LIBRARIES_LIBS_libterminatedThread += -lpthread
15041504
BUILD_HOTSPOT_JTREG_LIBRARIES_LIBS_libatExit += -ljvm
15051505
BUILD_HOTSPOT_JTREG_LIBRARIES_LIBS_libCompleteExit += -lpthread
1506+
BUILD_HOTSPOT_JTREG_LIBRARIES_LIBS_libMonitorWithDeadObjectTest += -lpthread
15061507
BUILD_HOTSPOT_JTREG_LIBRARIES_LIBS_libnativeStack += -lpthread
15071508
BUILD_HOTSPOT_JTREG_EXECUTABLES_LIBS_exeGetCreatedJavaVMs := -ljvm -lpthread
15081509

‎src/hotspot/share/prims/jvmtiEnvBase.cpp

+7
Original file line numberDiff line numberDiff line change
@@ -2243,6 +2243,13 @@ JvmtiMonitorClosure::do_monitor(ObjectMonitor* mon) {
22432243
}
22442244
// Filter out on stack monitors collected during stack walk.
22452245
oop obj = mon->object();
2246+
2247+
if (obj == nullptr) {
2248+
// This can happen if JNI code drops all references to the
2249+
// owning object.
2250+
return;
2251+
}
2252+
22462253
bool found = false;
22472254
for (int j = 0; j < _owned_monitors_list->length(); j++) {
22482255
jobject jobj = ((jvmtiMonitorStackDepthInfo*)_owned_monitors_list->at(j))->monitor;

‎src/hotspot/share/runtime/synchronizer.cpp

-1
Original file line numberDiff line numberDiff line change
@@ -1115,7 +1115,6 @@ void ObjectSynchronizer::owned_monitors_iterate_filtered(MonitorClosure* closure
11151115
// ObjectMonitor cannot be async deflated.
11161116
if (monitor->has_owner() && filter(monitor->owner_raw())) {
11171117
assert(!monitor->is_being_async_deflated(), "Owned monitors should not be deflating");
1118-
assert(monitor->object_peek() != nullptr, "Owned monitors should not have a dead object");
11191118

11201119
closure->do_monitor(monitor);
11211120
}

‎src/hotspot/share/runtime/vmOperations.cpp

+6
Original file line numberDiff line numberDiff line change
@@ -347,6 +347,12 @@ class ObjectMonitorsDump : public MonitorClosure, public ObjectMonitorsView {
347347
return;
348348
}
349349

350+
if (monitor->object_peek() == nullptr) {
351+
// JNI code doesn't necessarily keep the monitor object
352+
// alive. Filter out monitors with dead objects.
353+
return;
354+
}
355+
350356
add(monitor);
351357
}
352358

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
/*
2+
* Copyright (c) 2022, 2023, 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+
25+
/*
26+
* @bug 8320515
27+
* @summary This test checks that ObjectMonitors with dead objects don't
28+
* cause asserts, crashes, or failures when various sub-systems
29+
* in the JVM find them.
30+
* @library /testlibrary /test/lib
31+
* @modules jdk.management
32+
*/
33+
34+
/*
35+
* @requires os.family != "windows" & os.family != "aix"
36+
* @test id=DetachThread
37+
* @run main/othervm/native MonitorWithDeadObjectTest 0
38+
*/
39+
40+
/*
41+
* @requires os.family != "windows" & os.family != "aix"
42+
* @test id=DumpThreadsBeforeDetach
43+
* @run main/othervm/native MonitorWithDeadObjectTest 1
44+
*/
45+
46+
/*
47+
* @requires os.family != "windows" & os.family != "aix"
48+
* @test id=DumpThreadsAfterDetach
49+
* @run main/othervm/native MonitorWithDeadObjectTest 2
50+
*/
51+
52+
import java.lang.management.ManagementFactory;
53+
import java.lang.management.ThreadMXBean;
54+
55+
public class MonitorWithDeadObjectTest {
56+
public static native void createMonitorWithDeadObject();
57+
public static native void createMonitorWithDeadObjectDumpThreadsBeforeDetach();
58+
59+
static {
60+
System.loadLibrary("MonitorWithDeadObjectTest");
61+
}
62+
63+
private static void dumpThreadsWithLockedMonitors() {
64+
ThreadMXBean threadBean = ManagementFactory.getThreadMXBean();
65+
threadBean.dumpAllThreads(true, false);
66+
}
67+
68+
private static void testDetachThread() {
69+
// Create an ObjectMonitor with a dead object from an attached thread.
70+
// This used to provoke an assert in DetachCurrentThread.
71+
createMonitorWithDeadObject();
72+
}
73+
74+
private static void testDumpThreadsBeforeDetach() {
75+
// Create an ObjectMonitor with a dead object from an attached thread
76+
// and perform a thread dump before detaching the thread.
77+
createMonitorWithDeadObjectDumpThreadsBeforeDetach();
78+
}
79+
80+
private static void testDumpThreadsAfterDetach() {
81+
createMonitorWithDeadObject();
82+
83+
// The thread dumping code used to not tolerate monitors with dead
84+
// objects and the detach code used to not unlock these monitors, so
85+
// test that we don't end up with a bug where these monitors are not
86+
// unlocked and then passed to the thread dumping code.
87+
dumpThreadsWithLockedMonitors();
88+
}
89+
90+
public static void main(String[] args) throws Exception {
91+
int test = Integer.parseInt(args[0]);
92+
switch (test) {
93+
case 0: testDetachThread(); break;
94+
case 1: testDumpThreadsBeforeDetach(); break;
95+
case 2: testDumpThreadsAfterDetach(); break;
96+
default: throw new RuntimeException("Unknown test");
97+
};
98+
}
99+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
/*
2+
* Copyright (c) 2022, 2023, 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+
#include <jni.h>
25+
#include <pthread.h>
26+
#include <stdio.h>
27+
#include <stdlib.h>
28+
#include <unistd.h>
29+
30+
#ifdef __cplusplus
31+
extern "C" {
32+
#endif
33+
34+
static JavaVM* jvm;
35+
static pthread_t attacher;
36+
37+
#define die(x) do { printf("%s:%s\n",x , __func__); perror(x); exit(EXIT_FAILURE); } while (0)
38+
39+
static void check_exception(JNIEnv* env, const char* msg) {
40+
if ((*env)->ExceptionCheck(env)) {
41+
fprintf(stderr, "Error: %s", msg);
42+
exit(-1);
43+
}
44+
}
45+
46+
#define check(env, what, msg) \
47+
check_exception((env), (msg)); \
48+
do { \
49+
if ((what) == 0) { \
50+
fprintf(stderr, #what "is null: %s", (msg)); \
51+
exit(-2); \
52+
} \
53+
} while (0)
54+
55+
static jobject create_object(JNIEnv* env) {
56+
jclass clazz = (*env)->FindClass(env, "java/lang/Object");
57+
check(env, clazz, "No class");
58+
59+
jmethodID constructor = (*env)->GetMethodID(env, clazz, "<init>", "()V");
60+
check(env, constructor, "No constructor");
61+
62+
jobject obj = (*env)->NewObject(env, clazz, constructor);
63+
check(env, constructor, "No object");
64+
65+
return obj;
66+
}
67+
68+
static void system_gc(JNIEnv* env) {
69+
jclass clazz = (*env)->FindClass(env, "java/lang/System");
70+
check(env, clazz, "No class");
71+
72+
jmethodID method = (*env)->GetStaticMethodID(env, clazz, "gc", "()V");
73+
check(env, method, "No method");
74+
75+
(*env)->CallStaticVoidMethod(env, clazz, method);
76+
check_exception(env, "Calling System.gc()");
77+
}
78+
79+
static void thread_dump_with_locked_monitors(JNIEnv* env) {
80+
jclass ManagementFactoryClass = (*env)->FindClass(env, "java/lang/management/ManagementFactory");
81+
check(env, ManagementFactoryClass, "No ManagementFactory class");
82+
83+
jmethodID getThreadMXBeanMethod = (*env)->GetStaticMethodID(env, ManagementFactoryClass, "getThreadMXBean", "()Ljava/lang/management/ThreadMXBean;");
84+
check(env, getThreadMXBeanMethod, "No getThreadMXBean method");
85+
86+
jobject threadBean = (*env)->CallStaticObjectMethod(env, ManagementFactoryClass, getThreadMXBeanMethod);
87+
check(env, threadBean, "Calling getThreadMXBean()");
88+
89+
jclass ThreadMXBeanClass = (*env)->FindClass(env, "java/lang/management/ThreadMXBean");
90+
check(env, ThreadMXBeanClass, "No ThreadMXBean class");
91+
92+
jmethodID dumpAllThreadsMethod = (*env)->GetMethodID(env, ThreadMXBeanClass, "dumpAllThreads", "(ZZ)[Ljava/lang/management/ThreadInfo;");
93+
check(env, dumpAllThreadsMethod, "No dumpAllThreads method");
94+
95+
// The 'lockedMonitors == true' is what causes the monitor with a dead object to be examined.
96+
jobject array = (*env)->CallObjectMethod(env, threadBean, dumpAllThreadsMethod, JNI_TRUE /* lockedMonitors */, JNI_FALSE /* lockedSynchronizers*/);
97+
check(env, array, "Calling dumpAllThreads(true, false)");
98+
}
99+
100+
static void create_monitor_with_dead_object(JNIEnv* env) {
101+
jobject obj = create_object(env);
102+
103+
if ((*env)->MonitorEnter(env, obj) != 0) die("MonitorEnter");
104+
105+
// Drop the last strong reference to the object associated with the monitor.
106+
// The monitor only keeps a weak reference to the object.
107+
(*env)->DeleteLocalRef(env, obj);
108+
109+
// Let the GC clear the weak reference to the object.
110+
system_gc(env);
111+
}
112+
113+
static void* create_monitor_with_dead_object_in_thread(void* arg) {
114+
JNIEnv* env;
115+
int res = (*jvm)->AttachCurrentThread(jvm, (void**)&env, NULL);
116+
if (res != JNI_OK) die("AttachCurrentThread");
117+
118+
// Make the correct incantation to create a monitor with a dead object.
119+
create_monitor_with_dead_object(env);
120+
121+
// DetachCurrentThread will try to unlock held monitors. This has been a
122+
// source of at least two bugs:
123+
// - When the object reference in the monitor was cleared, the monitor
124+
// iterator code would skip it, preventing it from being unlocked when
125+
// the owner thread detached, leaving it lingering in the system.
126+
// - When the monitor iterator API was rewritten the code was changed to
127+
// assert that we didn't have "owned" monitors with dead objects. This
128+
// test provokes that situation and that asserts.
129+
if ((*jvm)->DetachCurrentThread(jvm) != JNI_OK) die("DetachCurrentThread");
130+
131+
return NULL;
132+
}
133+
134+
static void* create_monitor_with_dead_object_and_dump_threads_in_thread(void* arg) {
135+
JNIEnv* env;
136+
int res = (*jvm)->AttachCurrentThread(jvm, (void**)&env, NULL);
137+
if (res != JNI_OK) die("AttachCurrentThread");
138+
139+
// Make the correct incantation to create a monitor with a dead object.
140+
create_monitor_with_dead_object(env);
141+
142+
// Perform a thread dump that checks for all thread's monitors.
143+
// That code didn't expect the monitor iterators to return monitors
144+
// with dead objects and therefore asserted/crashed.
145+
thread_dump_with_locked_monitors(env);
146+
147+
if ((*jvm)->DetachCurrentThread(jvm) != JNI_OK) die("DetachCurrentThread");
148+
149+
return NULL;
150+
}
151+
152+
JNIEXPORT void JNICALL Java_MonitorWithDeadObjectTest_createMonitorWithDeadObject(JNIEnv* env, jclass jc) {
153+
void* ret;
154+
155+
(*env)->GetJavaVM(env, &jvm);
156+
157+
if (pthread_create(&attacher, NULL, create_monitor_with_dead_object_in_thread, NULL) != 0) die("pthread_create");
158+
if (pthread_join(attacher, &ret) != 0) die("pthread_join");
159+
}
160+
161+
JNIEXPORT void JNICALL Java_MonitorWithDeadObjectTest_createMonitorWithDeadObjectDumpThreadsBeforeDetach(JNIEnv* env, jclass jc) {
162+
void* ret;
163+
164+
(*env)->GetJavaVM(env, &jvm);
165+
166+
if (pthread_create(&attacher, NULL, create_monitor_with_dead_object_and_dump_threads_in_thread, NULL) != 0) die("pthread_create");
167+
if (pthread_join(attacher, &ret) != 0) die("pthread_join");
168+
}
169+
170+
#ifdef __cplusplus
171+
}
172+
#endif

‎test/hotspot/jtreg/serviceability/jvmti/GetOwnedMonitorInfo/GetOwnedMonitorInfoTest.java

+30-5
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,10 @@
2424

2525
/**
2626
* @test
27-
* @bug 8185164
28-
* @summary Checks that a contended monitor does not show up in the list of owned monitors
27+
* @bug 8185164 8320515
28+
* @summary Checks that a contended monitor does not show up in the list of owned monitors.
29+
* 8320515 piggy-backs on this test and injects an owned monitor with a dead object,
30+
and checks that that monitor isn't exposed to GetOwnedMonitorInfo.
2931
* @requires vm.jvmti
3032
* @compile GetOwnedMonitorInfoTest.java
3133
* @run main/othervm/native -agentlib:GetOwnedMonitorInfoTest GetOwnedMonitorInfoTest
@@ -46,19 +48,42 @@ public class GetOwnedMonitorInfoTest {
4648
}
4749
}
4850

51+
private static native void jniMonitorEnter(Object obj);
4952
private static native int check();
5053
private static native boolean hasEventPosted();
5154

55+
private static void jniMonitorEnterAndLetObjectDie() {
56+
// The monitor iterator used by GetOwnedMonitorInfo used to
57+
// assert when an owned monitor with a dead object was found.
58+
// Inject this situation into this test that performs other
59+
// GetOwnedMonitorInfo testing.
60+
Object obj = new Object() {};
61+
jniMonitorEnter(obj);
62+
if (!Thread.holdsLock(obj)) {
63+
throw new RuntimeException("The object is not locked");
64+
}
65+
obj = null;
66+
System.gc();
67+
}
68+
5269
public static void main(String[] args) throws Exception {
53-
runTest(true);
54-
runTest(false);
70+
runTest(true, true);
71+
runTest(true, false);
72+
runTest(false, true);
73+
runTest(false, false);
5574
}
5675

57-
public static void runTest(boolean isVirtual) throws Exception {
76+
public static void runTest(boolean isVirtual, boolean jni) throws Exception {
5877
var threadFactory = isVirtual ? Thread.ofVirtual().factory() : Thread.ofPlatform().factory();
5978
final GetOwnedMonitorInfoTest lock = new GetOwnedMonitorInfoTest();
6079

6180
Thread t1 = threadFactory.newThread(() -> {
81+
Thread.currentThread().setName("Worker-Thread");
82+
83+
if (jni) {
84+
jniMonitorEnterAndLetObjectDie();
85+
}
86+
6287
synchronized (lock) {
6388
System.out.println("Thread in sync section: "
6489
+ Thread.currentThread().getName());

‎test/hotspot/jtreg/serviceability/jvmti/GetOwnedMonitorInfo/libGetOwnedMonitorInfoTest.c

+9
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
*/
2323

2424
#include <stdio.h>
25+
#include <stdlib.h>
2526
#include <string.h>
2627
#include "jvmti.h"
2728
#include "jni.h"
@@ -264,6 +265,14 @@ jint Agent_Initialize(JavaVM *jvm, char *options, void *reserved) {
264265
return JNI_OK;
265266
}
266267

268+
JNIEXPORT void JNICALL
269+
Java_GetOwnedMonitorInfoTest_jniMonitorEnter(JNIEnv* env, jclass cls, jobject obj) {
270+
if ((*env)->MonitorEnter(env, obj) != 0) {
271+
fprintf(stderr, "MonitorEnter failed");
272+
exit(-1);
273+
}
274+
}
275+
267276
JNIEXPORT jint JNICALL
268277
Java_GetOwnedMonitorInfoTest_check(JNIEnv *env, jclass cls) {
269278
return status;

0 commit comments

Comments
 (0)
Please sign in to comment.