With the introduction of virtual threads (JDK-8284161) a number of fields in java.lang.Thread were moved into a separate data structure, the FieldHolder class, so that only platform threads would have these. Unfortunately the logic in the VM to create the java.lang.Thread instance when attaching a native thread doesn't account for this properly as it can try to use these fields even if the Thread constructor threw an exception and failed to create the FieldHolder:
void JavaThread::allocate_threadObj(Handle thread_group, const char* thread_name,
bool daemon, TRAPS) {
...
if (thread_name != nullptr) {
Handle name = java_lang_String::create_from_str(thread_name, CHECK);
// Thread gets assigned specified name and null target
JavaCalls::call_special(&result,
...
THREAD);
} else {
JavaCalls::call_special(&result,
...
THREAD);
}
<= Exception could be pending at this point due to use of THREAD not CHECK
os::set_priority(this, NormPriority);
if (daemon) {
java_lang_Thread::set_daemon(thread_oop()); <== Could try to set null FieldHolder here
}
Further, java_lang_Thread::is_daemon() also expects the FieldHolder to be set if you have a non-null Thread oop, but that is not guaranteed.
This problem manifests as a failed assertion in a debug build, when calling set_daemon, as the FieldHolder field is null. Crash reporting then calls is-daemon which fails the same assert and so we get a secondary crash.