JDK-8320515 : assert(monitor->object_peek() != nullptr) failed: Owned monitors should not have a dead object
  • Type: Bug
  • Component: hotspot
  • Sub-Component: runtime
  • Affected Version: 22
  • Priority: P3
  • Status: Resolved
  • Resolution: Fixed
  • Submitted: 2023-11-21
  • Updated: 2024-03-18
  • Resolved: 2023-11-30
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
JDK 21 JDK 22
21.0.4-oracleFixed 22 b27Fixed
Related Reports
Relates :  
Relates :  
Relates :  
Relates :  
Relates :  
Description
We hit this assert when JNI code calls MonitorEnter, makes the object unreachable, and then detaches the thread without calling MonitorExit.

This can be seen by running the CompleteExit test with `-XX:+UseZGC -Xmx12m` and the following changes:
```
diff --git a/test/hotspot/jtreg/runtime/Monitor/libCompleteExit.c b/test/hotspot/jtreg/runtime/Monitor/libCompleteExit.c
index 07ba0ff0ef8..fac500542ea 100644
--- a/test/hotspot/jtreg/runtime/Monitor/libCompleteExit.c
+++ b/test/hotspot/jtreg/runtime/Monitor/libCompleteExit.c
@@ -44,8 +44,27 @@ static void* do_test() {
   int res = (*jvm)->AttachCurrentThread(jvm, (void**)&env, NULL);
   if (res != JNI_OK) die("AttachCurrentThread");
 
-  if ((*env)->MonitorEnter(env, t1) != 0) die("MonitorEnter");
-  if ((*env)->MonitorEnter(env, t2) != 0) die("MonitorEnter");
+  jclass c = (*env)->FindClass(env, "java/lang/Object");
+  if (c == 0) {
+    die("No class");
+  }
+
+  jmethodID m = (*env)->GetMethodID(env, c, "<init>", "()V");
+  if (m == 0) {
+    die("No constructor");
+  }
+
+  jobject o1 = (*env)->NewObject(env, c, m);
+  jobject o2 = (*env)->NewObject(env, c, m);
+  jobject o3 = (*env)->NewObject(env, c, m);
+
+  if ((*env)->MonitorEnter(env, o1) != 0) die("MonitorEnter");
+  if ((*env)->MonitorEnter(env, o2) != 0) die("MonitorEnter");
+
+  (*env)->DeleteLocalRef(env, o1);
+  (*env)->DeleteLocalRef(env, o2);
+
+  (*env)->NewObjectArray(env, 1024 * 1024, c, o3);
 
   if ((*jvm)->DetachCurrentThread(jvm) != JNI_OK) die("DetachCurrentThread");
   pthread_exit(NULL);
```

The assert comes from this path:

V  [libjvm.so+0x171fb1d]  ObjectSynchronizer::owned_monitors_iterate(MonitorClosure*, JavaThread*)+0x15d  (synchronizer.cpp:1094)
V  [libjvm.so+0x1720d2a]  ObjectSynchronizer::release_monitors_owned_by_thread(JavaThread*)+0x4a  (synchronizer.cpp:1792)
V  [libjvm.so+0xeb806c]  JavaThread::exit(bool, JavaThread::ExitType)+0x7fc  (javaThread.cpp:871)
V  [libjvm.so+0xf8066a]  jni_DetachCurrentThread+0xda  (jni.cpp:3923)
C  [libCompleteExit.so+0xb53]  do_test+0x173  (libCompleteExit.c:73)

I've also managed to reproduce this issue from VM_ThreadDump with this patch:
```
diff --git a/src/hotspot/share/runtime/synchronizer.cpp b/src/hotspot/share/runtime/synchronizer.cpp
index ed323b7a416..7bf8091a4e6 100644
--- a/src/hotspot/share/runtime/synchronizer.cpp
+++ b/src/hotspot/share/runtime/synchronizer.cpp
@@ -1091,7 +1091,7 @@ void ObjectSynchronizer::owned_monitors_iterate_filtered(MonitorClosure* closure
     // ObjectMonitor cannot be async deflated.
     if (monitor->has_owner() && filter(monitor->owner_raw())) {
       assert(!monitor->is_being_async_deflated(), "Owned monitors should not be deflating");
-      assert(monitor->object_peek() != nullptr, "Owned monitors should not have a dead object");
+      assert(UseNewCode || monitor->object_peek() != nullptr, "Owned monitors should not have a dead object");
 
       closure->do_monitor(monitor);
     }
diff --git a/test/hotspot/jtreg/runtime/Monitor/CompleteExit.java b/test/hotspot/jtreg/runtime/Monitor/CompleteExit.java
index 7e63050dc9e..11b6149978d 100644
--- a/test/hotspot/jtreg/runtime/Monitor/CompleteExit.java
+++ b/test/hotspot/jtreg/runtime/Monitor/CompleteExit.java
@@ -31,6 +31,9 @@
  * @run main/native CompleteExit
  */
 
+import java.lang.management.ManagementFactory;
+import java.lang.management.ThreadMXBean;
+
 public class CompleteExit {
     public static native void testIt(Object o1, Object o2);
 
@@ -41,7 +44,21 @@ public class CompleteExit {
         System.loadLibrary("CompleteExit");
     }
 
+    static private void dumpThreads() {
+        ThreadMXBean threadBean = ManagementFactory.getThreadMXBean();
+        while (true) {
+            threadBean.dumpAllThreads(true, false);
+            try {
+                Thread.sleep(1);
+            } catch (InterruptedException e) {}
+        }
+    }
+
     public static void main(String[] args) throws Exception {
+        Thread threadDumper = new Thread(() -> dumpThreads());
+        threadDumper.setDaemon(true);
+        threadDumper.start();
+
         testIt(o1, o2);
     }
 }
diff --git a/test/hotspot/jtreg/runtime/Monitor/libCompleteExit.c b/test/hotspot/jtreg/runtime/Monitor/libCompleteExit.c
index 07ba0ff0ef8..6b1a5465b2e 100644
--- a/test/hotspot/jtreg/runtime/Monitor/libCompleteExit.c
+++ b/test/hotspot/jtreg/runtime/Monitor/libCompleteExit.c
@@ -25,6 +25,7 @@
 #include <stdlib.h>
 #include <pthread.h>
 #include <stdio.h>
+#include <unistd.h>
 
 #define die(x) do { printf("%s:%s\n",x , __func__); perror(x); exit(EXIT_FAILURE); } while (0)
 
@@ -44,8 +45,30 @@ static void* do_test() {
   int res = (*jvm)->AttachCurrentThread(jvm, (void**)&env, NULL);
   if (res != JNI_OK) die("AttachCurrentThread");
 
-  if ((*env)->MonitorEnter(env, t1) != 0) die("MonitorEnter");
-  if ((*env)->MonitorEnter(env, t2) != 0) die("MonitorEnter");
+  jclass c = (*env)->FindClass(env, "java/lang/Object");
+  if (c == 0) {
+    die("No class");
+  }
+
+  jmethodID m = (*env)->GetMethodID(env, c, "<init>", "()V");
+  if (m == 0) {
+    die("No constructor");
+  }
+
+  jobject o1 = (*env)->NewObject(env, c, m);
+  jobject o2 = (*env)->NewObject(env, c, m);
+  jobject o3 = (*env)->NewObject(env, c, m);
+
+  if ((*env)->MonitorEnter(env, o1) != 0) die("MonitorEnter");
+  if ((*env)->MonitorEnter(env, o2) != 0) die("MonitorEnter");
+
+  (*env)->DeleteLocalRef(env, o1);
+  (*env)->DeleteLocalRef(env, o2);
+
+  (*env)->NewObjectArray(env, 1024 * 1024, c, o3);
+  printf("Going to sleep: 10s");
+  sleep(10);
+  printf("Slept");
 
   if ((*jvm)->DetachCurrentThread(jvm) != JNI_OK) die("DetachCurrentThread");
   pthread_exit(NULL);
```
Comments
[jdk21u-fix-request] Approval Request from Aleksey Shipilëv Fixes the JNI interaction problem, followup for JDK-8318757. This is a part of atomic 21u integration, see 21u-dev PR for more details. Does not apply cleanly due to minor conflict in JtregNativeHotspot.gmk. All tests pass. Was in mainline for several months, without bugtail.
12-03-2024

A pull request was submitted for review. URL: https://git.openjdk.org/jdk21u-dev/pull/337 Date: 2024-03-06 19:12:00 +0000
07-03-2024

Changeset: 0d146361 Author: Stefan Karlsson <stefank@openjdk.org> Date: 2023-11-30 09:46:26 +0000 URL: https://git.openjdk.org/jdk/commit/0d146361f27e1415fab9272de1cdde84c074c718
30-11-2023

ILW = HLM = P3
28-11-2023

A pull request was submitted for review. URL: https://git.openjdk.org/jdk/pull/16783 Date: 2023-11-22 15:00:29 +0000
22-11-2023

Thanks to [~dholmes] who identified this issue.
21-11-2023