JDK-8308341 : JNI_GetCreatedJavaVMs returns a partially initialized JVM
  • Type: Bug
  • Component: hotspot
  • Sub-Component: runtime
  • Affected Version: 21
  • Priority: P3
  • Status: Resolved
  • Resolution: Fixed
  • OS: generic
  • CPU: generic
  • Submitted: 2023-05-15
  • Updated: 2025-05-06
  • Resolved: 2023-05-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 11 JDK 17 JDK 21
11.0.28-oracleFixed 17.0.15-oracleFixed 21 b25Fixed
Related Reports
CSR :  
Causes :  
Relates :  
Relates :  
Relates :  
Sub Tasks
JDK-8309249 :  
Description
ADDITIONAL SYSTEM INFORMATION :
OS: Ubuntu 22.04.2 LTS
OpenJDK compiled with debugslow from https://github.com/openjdk/jdk/ and commit 8d49ba9e8d3095f850b3007b56488a0c0cf8ddff

openjdk version "21-internal" 2023-09-19
OpenJDK Runtime Environment (slowdebug build 21-internal-adhoc.fpoli.jdk)
OpenJDK 64-Bit Server VM (slowdebug build 21-internal-adhoc.fpoli.jdk, mixed mode, sharing)

A DESCRIPTION OF THE PROBLEM :
`JNI_GetCreatedJavaVMs` returns a pointer to a VM that has not been fully initialized. Thus, trying to `AttachCurrentThread` on the returned VM causes an internal assertion to fail.


STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Run `make run`. The program will crash with high probability (100% on my computer so far).

ACTUAL -
The call to `JNI_GetCreatedJavaVMs` returns a pointer to a VM that has not been fully initialized. In fact, by calling `AttachCurrentThread` on the returned VM the following debug assertion fails:

```
#
# A fatal error has been detected by the Java Runtime Environment:
#
#  Internal Error (/home/fpoli/src/jdk/src/hotspot/share/runtime/mutex.cpp:275), pid=2128085, tid=2128087
#  assert(os::mutex_init_done()) failed: Too early!
#
# JRE version:  (21.0) (slowdebug build )
# Java VM: OpenJDK 64-Bit Server VM (slowdebug 21-internal-adhoc.fpoli.jdk, mixed mode, sharing, tiered, compressed class ptrs, unknown gc, linux-amd64)
# Core dump will be written. Default location: Core dumps may be processed with "/usr/share/apport/apport -p%p -s%s -c%c -d%d -P%P -u%u -g%g -- %E" (or dumping to /home/fpoli/src/jni-bug/core.2128085)
#
# An error report file with more information is saved as:
# /home/fpoli/src/jni-bug/hs_err_pid2128085.log
```

---------- BEGIN SOURCE ----------
C++ code:

```
#include <jni.h>
#include <string.h>
#include <pthread.h>
#include <iostream>

using namespace std;

#define NUM_THREADS 2

void *thread_runner(void *threadid) {
    long tid;
    tid = (long)threadid;

    JavaVM *vm;
    JNIEnv *env;

    JavaVMInitArgs vm_args;
    JavaVMOption* options = new JavaVMOption[0];
    vm_args.version = JNI_VERSION_1_6;
    vm_args.nOptions = 0;
    vm_args.options = options;
    vm_args.ignoreUnrecognized = false;

    cout << "[" << tid << "] BEGIN JNI_CreateJavaVM" << endl;
    jint create_res = JNI_CreateJavaVM(&vm, (void **)&env, &vm_args);
    cout << "[" << tid << "] END JNI_CreateJavaVM" << endl;

    if (create_res != JNI_OK) {
        cout << "[" << tid << "] Error creating JVM: " << create_res << endl;
        if (create_res == -5) {
            jsize count;

            cout << "[" << tid << "] BEGIN JNI_CreateJavaVM" << endl;
            jint get_res = JNI_GetCreatedJavaVMs(&vm, 1, &count);
            cout << "[" << tid << "] END JNI_CreateJavaVM" << endl;

            if (get_res != JNI_OK) {
                cout << "[" << tid << "] Error obtaining created JVMs: " << get_res << endl;
                pthread_exit(NULL);
            } else {
                cout << "[" << tid << "] Obtained an existing JVM" << endl;
            }

            cout << "[" << tid << "] BEGIN AttachCurrentThread" << endl;
            vm->AttachCurrentThread((void **)&env, NULL);
            cout << "[" << tid << "] END AttachCurrentThread" << endl;
        } else {
            pthread_exit(NULL);
        }
    } else {
        cout << "[" << tid << "] Created a JVM" << endl;
    }
    delete options;

    pthread_exit(NULL);
}

int main () {
    pthread_t threads[NUM_THREADS];
    for (int i = 0; i < NUM_THREADS; i++ ) {
        cout << "[*] Creating thread " << i << endl;
        int status = pthread_create(&threads[i], NULL, thread_runner, (void *)i);
        if (status) {
            cout << "[*] Error creating thread: " << status << endl;
            exit(-1);
        }
    }
    for (int i = 0; i < NUM_THREADS; i++ ) {
        pthread_join(threads[i], NULL);
        cout << "[*] Joined thread " << i << endl;
    }
    return 0;
}
```

Makefile:
```
main:
	$(CXX) \
		-Wall -Wextra -g \
		-L${JAVA_HOME}/lib/server/ \
		-I${JAVA_HOME}/include/ \
		-I${JAVA_HOME}/include/linux/ \
		main.cpp \
		-ljvm

run: main
	LD_LIBRARY_PATH="${JAVA_HOME}/lib/server/:${LD_LIBRARY_PATH}" ./a.out
```
---------- END SOURCE ----------

CUSTOMER SUBMITTED WORKAROUND :
Unknown

FREQUENCY : often



Comments
Fix request [17u] I backport this for parity with 17.0.15-oracle including follow-ups JDK-8309231 and JDK-8309171. Medium risk, VM startup was always hairy to change. But the VM should behave consistent with other VMs. Also, it's well tested. Trivial resolves. Test passes. SAP nighlty testing passed.
04-12-2024

A pull request was submitted for review. Branch: master URL: https://git.openjdk.org/jdk17u-dev/pull/3084 Date: 2024-12-02 13:08:05 +0000
02-12-2024

Changeset: 1e6770fb Author: David Holmes <dholmes@openjdk.org> Date: 2023-05-30 22:46:06 +0000 URL: https://git.openjdk.org/jdk/commit/1e6770fb978e630b38a70a05120c50f723bb66dc
30-05-2023

A pull request was submitted for review. URL: https://git.openjdk.org/jdk/pull/14139 Date: 2023-05-25 05:02:19 +0000
25-05-2023

Reopening. As I wrote above: The problem, per the bug report, is that GetCreatedJavaVMs is reporting too early that a JVM is available. It relies solely on the value of vm_created, but that is set to 1 early in the creation process as the guard against concurrent creation attempts - long before the VM initialization is actually complete. I have a fix in-progress but there is a subtle change in behaviour that might cause racy code that has been working (by luck) to start failing.
24-05-2023

Additional Information from submitter: ============================= When compiling and running the provided program using OpenJDK version 20.0.1 (see info below), the JVM terminates with a segmentation fault. Running the program with `gdb` shows the following backtrace: ``` (gdb) run Starting program: <path>/a.out [Thread debugging using libthread_db enabled] Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1". [*] Creating thread 0 [New Thread 0x7ffff63ff640 (LWP 2326275)] [*] Creating thread 1 [0] BEGIN JNI_CreateJavaVM [New Thread 0x7ffff5bfe640 (LWP 2326276)] [1] BEGIN JNI_CreateJavaVM [1] END JNI_CreateJavaVM [1] Error creating JVM: -5 [1] BEGIN JNI_CreateJavaVM [1] END JNI_CreateJavaVM [1] Obtained an existing JVM [1] BEGIN AttachCurrentThread Thread 3 "a.out" received signal SIGSEGV, Segmentation fault. [Switching to Thread 0x7ffff5bfe640 (LWP 2326276)] 0x00007ffff7a5bd46 in ThreadLocalAllocBuffer::initial_desired_size() () from /opt/openjdk-20.0.1/lib/server/libjvm.so (gdb) bt #0 0x00007ffff7a5bd46 in ThreadLocalAllocBuffer::initial_desired_size() () from /opt/openjdk-20.0.1/lib/server/libjvm.so #1 0x00007ffff7a5bdb4 in ThreadLocalAllocBuffer::initialize() () from /opt/openjdk-20.0.1/lib/server/libjvm.so #2 0x00007ffff7559524 in attach_current_thread.part () from /opt/openjdk-20.0.1/lib/server/libjvm.so #3 0x000055555555595a in JavaVM_::AttachCurrentThread (this=0x7ffff7eecf80 <main_vm>, penv=0x7ffff5bfdd98, args=0x0) at /opt/openjdk-20.0.1/include/jni.h:1950 #4 0x0000555555555676 in thread_runner (threadid=0x1) at main.cpp:45 #5 0x00007ffff6494b43 in start_thread (arg=<optimized out>) at ./nptl/pthread_create.c:442 #6 0x00007ffff6526a00 in clone3 () at ../sysdeps/unix/sysv/linux/x86_64/clone3.S:81 ``` JDK version: ``` openjdk version "20.0.1" 2023-04-18 OpenJDK Runtime Environment (build 20.0.1+9-29) OpenJDK 64-Bit Server VM (build 20.0.1+9-29, mixed mode, sharing) ```
24-05-2023

Runtime Triage team: Closing as not an issue based on David's comments. This is not a JVM issue.
23-05-2023

jint get_res = JNI_GetCreatedJavaVMs(&vm, 1, &count); cout << "[" << tid << "] END JNI_CreateJavaVM" << endl; if (get_res != JNI_OK) { cout << "[" << tid << "] Error obtaining created JVMs: " << get_res << endl; pthread_exit(NULL); } else { cout << "[" << tid << "] Obtained an existing JVM" << endl; } Also note the above code is incorrect. JNI_GetCreatedJavaVMs will return JNI_OK even if there are zero created VMs (zeroi is a valid answer so the call should not report an error). You need to check the value of count is one otherwise you are just using a random memory location as if it were a JavaVM structure.
19-05-2023

A workaround would be to use synchronization e.g. a semaphore, around any calls to CreateJavaVM, such that any secondary calls will have to wait until the primary has actually completed.
18-05-2023

I initially misread the test program and as there was no output shown I had the wrong impression of what is happening. Here is the output (with source code adjustments for correct method reporting): [*] Creating thread 0 [*] Creating thread 1 [0] BEGIN JNI_CreateJavaVM [1] BEGIN JNI_CreateJavaVM [1] END JNI_CreateJavaVM [1] Error creating JVM: -5 [1] BEGIN JNI_GetCreatedJavaVMs [1] END JNI_GetCreatedJavaVMs [1] Obtained 1 existing JVM [1] BEGIN AttachCurrentThread [53077813.102s][warning][os,thread] Attempt to allocate stack guard pages failed. Segmentation fault (core dumped) So what is happening is that thread[0] is in the process of initializing the JVM. Thread[1] attempts to create the VM as well and gets an error as expected (JNI_EEXIST). So it looks up the VM that is already in existence via GetCreatedJavaVMs and receives back the JavaVM that ithread[0] was creating, but apparently has not yet completed. thread[1] then tries to attach to that JavaVM and it fails. The problem, per the bug report, is that GetCreatedJavaVMs is reporting too early that a JVM is available. It relies solely on the value of vm_created, but that is set to 1 early in the creation process as the guard against concurrent creation attempts - long before the VM initialization is actually complete.
18-05-2023

The crash is reported on OpenJDK 21 crash occurred in the native thread: Current thread is native thread Stack: [0x00007f69ca000000,0x00007f69ca7ff000], sp=0x00007f69ca7fd7b0, free space=8181k Native frames: (J=compiled Java code, j=interpreted, Vv=VM code, C=native code) V [libjvm.so+0x16d8d2d] VMError::report_and_die(int, char const*, char const*, __va_list_tag*, Thread*, unsigned char*, void*, void*, char const*, int, unsigned long)+0x915 (mutex.cpp:275) V [libjvm.so+0x16d83bd] (vmError.cpp:1361) V [libjvm.so+0xa05117] report_vm_status_error(char const*, int, char const*, int, char const*)+0x0 (debug.cpp:191) V [libjvm.so+0x12874c7] Mutex::Mutex(Mutex::Rank, char const*, bool)+0x99 (mutex.cpp:275) Moving it to dev team for further analysis.
18-05-2023