8333542: Breakpoint in parallel code does not work

Co-authored-by: Chris Plummer <cjplummer@openjdk.org>
Reviewed-by: dholmes, vlivanov
This commit is contained in:
Coleen Phillimore 2024-06-25 19:50:58 +00:00
parent 86b0cf259f
commit b3bf31a0a0
16 changed files with 387 additions and 177 deletions

View File

@ -788,6 +788,7 @@ int java_lang_Class::_class_loader_offset;
int java_lang_Class::_module_offset;
int java_lang_Class::_protection_domain_offset;
int java_lang_Class::_component_mirror_offset;
int java_lang_Class::_init_lock_offset;
int java_lang_Class::_signers_offset;
int java_lang_Class::_name_offset;
int java_lang_Class::_source_file_offset;
@ -911,6 +912,12 @@ void java_lang_Class::initialize_mirror_fields(Klass* k,
Handle protection_domain,
Handle classData,
TRAPS) {
// Allocate a simple java object for a lock.
// This needs to be a java object because during class initialization
// it can be held across a java call.
typeArrayOop r = oopFactory::new_typeArray(T_INT, 0, CHECK);
set_init_lock(mirror(), r);
// Set protection domain also
set_protection_domain(mirror(), protection_domain());
@ -1132,6 +1139,10 @@ bool java_lang_Class::restore_archived_mirror(Klass *k,
if (!k->is_array_klass()) {
// - local static final fields with initial values were initialized at dump time
// create the init_lock
typeArrayOop r = oopFactory::new_typeArray(T_INT, 0, CHECK_(false));
set_init_lock(mirror(), r);
if (protection_domain.not_null()) {
set_protection_domain(mirror(), protection_domain());
}
@ -1196,6 +1207,15 @@ oop java_lang_Class::component_mirror(oop java_class) {
return java_class->obj_field(_component_mirror_offset);
}
oop java_lang_Class::init_lock(oop java_class) {
assert(_init_lock_offset != 0, "must be set");
return java_class->obj_field(_init_lock_offset);
}
void java_lang_Class::set_init_lock(oop java_class, oop init_lock) {
assert(_init_lock_offset != 0, "must be set");
java_class->obj_field_put(_init_lock_offset, init_lock);
}
objArrayOop java_lang_Class::signers(oop java_class) {
assert(_signers_offset != 0, "must be set");
return (objArrayOop)java_class->obj_field(_signers_offset);
@ -1415,12 +1435,18 @@ void java_lang_Class::compute_offsets() {
InstanceKlass* k = vmClasses::Class_klass();
CLASS_FIELDS_DO(FIELD_COMPUTE_OFFSET);
// Init lock is a C union with component_mirror. Only instanceKlass mirrors have
// init_lock and only ArrayKlass mirrors have component_mirror. Since both are oops
// GC treats them the same.
_init_lock_offset = _component_mirror_offset;
CLASS_INJECTED_FIELDS(INJECTED_FIELD_COMPUTE_OFFSET);
}
#if INCLUDE_CDS
void java_lang_Class::serialize_offsets(SerializeClosure* f) {
f->do_bool(&_offsets_computed);
f->do_u4((u4*)&_init_lock_offset);
CLASS_FIELDS_DO(FIELD_SERIALIZE_OFFSET);

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 1997, 2023, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 1997, 2024, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -226,6 +226,7 @@ class java_lang_Class : AllStatic {
static int _static_oop_field_count_offset;
static int _protection_domain_offset;
static int _init_lock_offset;
static int _signers_offset;
static int _class_loader_offset;
static int _module_offset;
@ -240,6 +241,7 @@ class java_lang_Class : AllStatic {
static GrowableArray<Klass*>* _fixup_mirror_list;
static GrowableArray<Klass*>* _fixup_module_field_list;
static void set_init_lock(oop java_class, oop init_lock);
static void set_protection_domain(oop java_class, oop protection_domain);
static void set_class_loader(oop java_class, oop class_loader);
static void set_component_mirror(oop java_class, oop comp_mirror);
@ -292,6 +294,10 @@ class java_lang_Class : AllStatic {
// Support for embedded per-class oops
static oop protection_domain(oop java_class);
static oop init_lock(oop java_class);
static void clear_init_lock(oop java_class) {
set_init_lock(java_class, nullptr);
}
static oop component_mirror(oop java_class);
static objArrayOop signers(oop java_class);
static void set_signers(oop java_class, objArrayOop signers);

View File

@ -557,6 +557,7 @@ class SerializeClosure;
template(bool_array_signature, "[Z") \
template(byte_array_signature, "[B") \
template(char_array_signature, "[C") \
template(int_array_signature, "[I") \
template(runnable_signature, "Ljava/lang/Runnable;") \
template(continuation_signature, "Ljdk/internal/vm/Continuation;") \
template(continuationscope_signature, "Ljdk/internal/vm/ContinuationScope;") \

View File

@ -1824,7 +1824,7 @@ void LinkResolver::resolve_invokedynamic(CallInfo& result, const constantPoolHan
// the interpreter or runtime performs a serialized check of
// the relevant ResolvedIndyEntry::method field. This is done by the caller
// of this method, via CPC::set_dynamic_call, which uses
// a lock to do the final serialization of updates
// an ObjectLocker to do the final serialization of updates
// to ResolvedIndyEntry state, including method.
// Log dynamic info to CDS classlist.

View File

@ -56,6 +56,7 @@
#include "runtime/atomic.hpp"
#include "runtime/handles.inline.hpp"
#include "runtime/mutexLocker.hpp"
#include "runtime/synchronizer.hpp"
#include "runtime/vm_version.hpp"
#include "utilities/macros.hpp"
@ -174,7 +175,7 @@ void ConstantPoolCache::set_direct_or_vtable_call(Bytecodes::Code invoke_code,
}
if (invoke_code == Bytecodes::_invokestatic) {
assert(method->method_holder()->is_initialized() ||
method->method_holder()->is_init_thread(JavaThread::current()),
method->method_holder()->is_reentrant_initialization(JavaThread::current()),
"invalid class initialization state for invoke_static");
if (!VM_Version::supports_fast_class_init_checks() && method->needs_clinit_barrier()) {
@ -269,11 +270,20 @@ ResolvedMethodEntry* ConstantPoolCache::set_method_handle(int method_index, cons
// A losing writer waits on the lock until the winner writes the method and leaves
// the lock, so that when the losing writer returns, he can use the linked
// cache entry.
// Lock fields to write
Bytecodes::Code invoke_code = Bytecodes::_invokehandle;
MutexLocker ml(constant_pool()->pool_holder()->init_monitor());
ResolvedMethodEntry* method_entry = resolved_method_entry_at(method_index);
JavaThread* current = JavaThread::current();
objArrayHandle resolved_references(current, constant_pool()->resolved_references());
// Use the resolved_references() lock for this cpCache entry.
// resolved_references are created for all classes with Invokedynamic, MethodHandle
// or MethodType constant pool cache entries.
assert(resolved_references() != nullptr,
"a resolved_references array should have been created for this class");
ObjectLocker ol(resolved_references, current);
ResolvedMethodEntry* method_entry = resolved_method_entry_at(method_index);
if (method_entry->is_resolved(invoke_code)) {
return method_entry;
}
@ -311,7 +321,6 @@ ResolvedMethodEntry* ConstantPoolCache::set_method_handle(int method_index, cons
// Store appendix, if any.
if (has_appendix) {
const int appendix_index = method_entry->resolved_references_index();
objArrayOop resolved_references = constant_pool()->resolved_references();
assert(appendix_index >= 0 && appendix_index < resolved_references->length(), "oob");
assert(resolved_references->obj_at(appendix_index) == nullptr, "init just once");
resolved_references->obj_at_put(appendix_index, appendix());
@ -587,7 +596,14 @@ bool ConstantPoolCache::save_and_throw_indy_exc(
assert(PENDING_EXCEPTION->is_a(vmClasses::LinkageError_klass()),
"No LinkageError exception");
MutexLocker ml(THREAD, cpool->pool_holder()->init_monitor());
// Use the resolved_references() lock for this cpCache entry.
// resolved_references are created for all classes with Invokedynamic, MethodHandle
// or MethodType constant pool cache entries.
JavaThread* current = THREAD;
objArrayHandle resolved_references(current, cpool->resolved_references());
assert(resolved_references() != nullptr,
"a resolved_references array should have been created for this class");
ObjectLocker ol(resolved_references, current);
// if the indy_info is resolved or the indy_resolution_failed flag is set then another
// thread either succeeded in resolving the method or got a LinkageError
@ -610,11 +626,21 @@ bool ConstantPoolCache::save_and_throw_indy_exc(
oop ConstantPoolCache::set_dynamic_call(const CallInfo &call_info, int index) {
ResourceMark rm;
MutexLocker ml(constant_pool()->pool_holder()->init_monitor());
// Use the resolved_references() lock for this cpCache entry.
// resolved_references are created for all classes with Invokedynamic, MethodHandle
// or MethodType constant pool cache entries.
JavaThread* current = JavaThread::current();
constantPoolHandle cp(current, constant_pool());
objArrayHandle resolved_references(current, cp->resolved_references());
assert(resolved_references() != nullptr,
"a resolved_references array should have been created for this class");
ObjectLocker ol(resolved_references, current);
assert(index >= 0, "Indy index must be positive at this point");
if (resolved_indy_entry_at(index)->method() != nullptr) {
return constant_pool()->resolved_reference_from_indy(index);
return cp->resolved_reference_from_indy(index);
}
if (resolved_indy_entry_at(index)->resolution_failed()) {
@ -622,9 +648,7 @@ oop ConstantPoolCache::set_dynamic_call(const CallInfo &call_info, int index) {
// resolution. Ignore our success and throw their exception.
guarantee(index >= 0, "Invalid indy index");
int encoded_index = ResolutionErrorTable::encode_indy_index(index);
JavaThread* THREAD = JavaThread::current(); // For exception macros.
constantPoolHandle cp(THREAD, constant_pool());
ConstantPool::throw_resolution_error(cp, encoded_index, THREAD);
ConstantPool::throw_resolution_error(cp, encoded_index, current);
return nullptr;
}
@ -648,7 +672,6 @@ oop ConstantPoolCache::set_dynamic_call(const CallInfo &call_info, int index) {
if (has_appendix) {
const int appendix_index = resolved_indy_entry_at(index)->resolved_references_index();
objArrayOop resolved_references = constant_pool()->resolved_references();
assert(appendix_index >= 0 && appendix_index < resolved_references->length(), "oob");
assert(resolved_references->obj_at(appendix_index) == nullptr, "init just once");
resolved_references->obj_at_put(appendix_index, appendix());

View File

@ -86,6 +86,7 @@
#include "runtime/orderAccess.hpp"
#include "runtime/os.inline.hpp"
#include "runtime/reflection.hpp"
#include "runtime/synchronizer.hpp"
#include "runtime/threads.hpp"
#include "services/classLoadingService.hpp"
#include "services/finalizerService.hpp"
@ -497,9 +498,6 @@ Array<int>* InstanceKlass::create_new_default_vtable_indices(int len, TRAPS) {
return vtable_indices;
}
static Monitor* create_init_monitor(const char* name) {
return new Monitor(Mutex::safepoint, name);
}
InstanceKlass::InstanceKlass() {
assert(CDSConfig::is_dumping_static_archive() || CDSConfig::is_using_archive(), "only for CDS");
@ -517,7 +515,6 @@ InstanceKlass::InstanceKlass(const ClassFileParser& parser, KlassKind kind, Refe
_nest_host_index(0),
_init_state(allocated),
_reference_type(reference_type),
_init_monitor(create_init_monitor("InstanceKlassInitMonitor_lock")),
_init_thread(nullptr)
{
set_vtable_length(parser.vtable_size());
@ -745,6 +742,28 @@ objArrayOop InstanceKlass::signers() const {
return java_lang_Class::signers(java_mirror());
}
oop InstanceKlass::init_lock() const {
// return the init lock from the mirror
oop lock = java_lang_Class::init_lock(java_mirror());
// Prevent reordering with any access of initialization state
OrderAccess::loadload();
assert(lock != nullptr || !is_not_initialized(), // initialized or in_error state
"only fully initialized state can have a null lock");
return lock;
}
// Set the initialization lock to null so the object can be GC'ed. Any racing
// threads to get this lock will see a null lock and will not lock.
// That's okay because they all check for initialized state after getting
// the lock and return.
void InstanceKlass::fence_and_clear_init_lock() {
// make sure previous stores are all done, notably the init_state.
OrderAccess::storestore();
java_lang_Class::clear_init_lock(java_mirror());
assert(!is_not_initialized(), "class must be initialized now");
}
// See "The Virtual Machine Specification" section 2.16.5 for a detailed explanation of the class initialization
// process. The step comments refers to the procedure described in that section.
// Note: implementation moved to static method to expose the this pointer.
@ -772,49 +791,6 @@ void InstanceKlass::link_class(TRAPS) {
}
}
void InstanceKlass::check_link_state_and_wait(JavaThread* current) {
MonitorLocker ml(current, _init_monitor);
bool debug_logging_enabled = log_is_enabled(Debug, class, init);
// Another thread is linking this class, wait.
while (is_being_linked() && !is_init_thread(current)) {
if (debug_logging_enabled) {
ResourceMark rm(current);
log_debug(class, init)("Thread \"%s\" waiting for linking of %s by thread \"%s\"",
current->name(), external_name(), init_thread_name());
}
ml.wait();
}
// This thread is recursively linking this class, continue
if (is_being_linked() && is_init_thread(current)) {
if (debug_logging_enabled) {
ResourceMark rm(current);
log_debug(class, init)("Thread \"%s\" recursively linking %s",
current->name(), external_name());
}
return;
}
// If this class wasn't linked already, set state to being_linked
if (!is_linked()) {
if (debug_logging_enabled) {
ResourceMark rm(current);
log_debug(class, init)("Thread \"%s\" linking %s",
current->name(), external_name());
}
set_init_state(being_linked);
set_init_thread(current);
} else {
if (debug_logging_enabled) {
ResourceMark rm(current);
log_debug(class, init)("Thread \"%s\" found %s already linked",
current->name(), external_name());
}
}
}
// Called to verify that a class can link during initialization, without
// throwing a VerifyError.
bool InstanceKlass::link_class_or_fail(TRAPS) {
@ -893,8 +869,9 @@ bool InstanceKlass::link_class_impl(TRAPS) {
// verification & rewriting
{
LockLinkState init_lock(this, jt);
HandleMark hm(THREAD);
Handle h_init_lock(THREAD, init_lock());
ObjectLocker ol(h_init_lock, jt);
// rewritten will have been set if loader constraint error found
// on an earlier link attempt
// don't verify or rewrite if already rewritten
@ -952,7 +929,21 @@ bool InstanceKlass::link_class_impl(TRAPS) {
// In case itable verification is ever added.
// itable().verify(tty, true);
#endif
set_initialization_state_and_notify(linked, THREAD);
if (UseVtableBasedCHA && Universe::is_fully_initialized()) {
DeoptimizationScope deopt_scope;
{
// Now mark all code that assumes the class is not linked.
// Set state under the Compile_lock also.
MutexLocker ml(THREAD, Compile_lock);
set_init_state(linked);
CodeCache::mark_dependents_on(&deopt_scope, this);
}
// Perform the deopt handshake outside Compile_lock.
deopt_scope.deoptimize_marked();
} else {
set_init_state(linked);
}
if (JvmtiExport::should_post_class_prepare()) {
JvmtiExport::post_class_prepare(THREAD, this);
}
@ -1084,7 +1075,6 @@ void InstanceKlass::initialize_impl(TRAPS) {
DTRACE_CLASSINIT_PROBE(required, -1);
bool wait = false;
bool throw_error = false;
JavaThread* jt = THREAD;
@ -1093,24 +1083,27 @@ void InstanceKlass::initialize_impl(TRAPS) {
// refer to the JVM book page 47 for description of steps
// Step 1
{
MonitorLocker ml(jt, _init_monitor);
Handle h_init_lock(THREAD, init_lock());
ObjectLocker ol(h_init_lock, jt);
// Step 2
while (is_being_initialized() && !is_init_thread(jt)) {
// If we were to use wait() instead of waitInterruptibly() then
// we might end up throwing IE from link/symbol resolution sites
// that aren't expected to throw. This would wreak havoc. See 6320309.
while (is_being_initialized() && !is_reentrant_initialization(jt)) {
if (debug_logging_enabled) {
ResourceMark rm(jt);
log_debug(class, init)("Thread \"%s\" waiting for initialization of %s by thread \"%s\"",
jt->name(), external_name(), init_thread_name());
}
wait = true;
jt->set_class_to_be_initialized(this);
ml.wait();
ol.wait_uninterruptibly(jt);
jt->set_class_to_be_initialized(nullptr);
}
// Step 3
if (is_being_initialized() && is_init_thread(jt)) {
if (is_being_initialized() && is_reentrant_initialization(jt)) {
if (debug_logging_enabled) {
ResourceMark rm(jt);
log_debug(class, init)("Thread \"%s\" recursively initializing %s",
@ -1138,7 +1131,19 @@ void InstanceKlass::initialize_impl(TRAPS) {
log_debug(class, init)("Thread \"%s\" found %s is in error state",
jt->name(), external_name());
}
throw_error = true;
DTRACE_CLASSINIT_PROBE_WAIT(erroneous, -1, wait);
ResourceMark rm(THREAD);
Handle cause(THREAD, get_initialization_error(THREAD));
stringStream ss;
ss.print("Could not initialize class %s", external_name());
if (cause.is_null()) {
THROW_MSG(vmSymbols::java_lang_NoClassDefFoundError(), ss.as_string());
} else {
THROW_MSG_CAUSE(vmSymbols::java_lang_NoClassDefFoundError(),
ss.as_string(), cause);
}
} else {
// Step 6
@ -1152,22 +1157,6 @@ void InstanceKlass::initialize_impl(TRAPS) {
}
}
// Throw error outside lock
if (throw_error) {
DTRACE_CLASSINIT_PROBE_WAIT(erroneous, -1, wait);
ResourceMark rm(THREAD);
Handle cause(THREAD, get_initialization_error(THREAD));
stringStream ss;
ss.print("Could not initialize class %s", external_name());
if (cause.is_null()) {
THROW_MSG(vmSymbols::java_lang_NoClassDefFoundError(), ss.as_string());
} else {
THROW_MSG_CAUSE(vmSymbols::java_lang_NoClassDefFoundError(),
ss.as_string(), cause);
}
}
// Step 7
// Next, if C is a class rather than an interface, initialize it's super class and super
// interfaces.
@ -1225,7 +1214,7 @@ void InstanceKlass::initialize_impl(TRAPS) {
// Step 9
if (!HAS_PENDING_EXCEPTION) {
set_initialization_state_and_notify(fully_initialized, THREAD);
set_initialization_state_and_notify(fully_initialized, CHECK);
debug_only(vtable().verify(tty, true);)
}
else {
@ -1258,43 +1247,26 @@ void InstanceKlass::initialize_impl(TRAPS) {
}
void InstanceKlass::set_initialization_state_and_notify(ClassState state, JavaThread* current) {
MonitorLocker ml(current, _init_monitor);
if (state == linked && UseVtableBasedCHA && Universe::is_fully_initialized()) {
DeoptimizationScope deopt_scope;
{
// Now mark all code that assumes the class is not linked.
// Set state under the Compile_lock also.
MutexLocker ml(current, Compile_lock);
set_init_thread(nullptr); // reset _init_thread before changing _init_state
set_init_state(state);
CodeCache::mark_dependents_on(&deopt_scope, this);
}
// Perform the deopt handshake outside Compile_lock.
deopt_scope.deoptimize_marked();
void InstanceKlass::set_initialization_state_and_notify(ClassState state, TRAPS) {
Handle h_init_lock(THREAD, init_lock());
if (h_init_lock() != nullptr) {
ObjectLocker ol(h_init_lock, THREAD);
set_init_thread(nullptr); // reset _init_thread before changing _init_state
set_init_state(state);
fence_and_clear_init_lock();
ol.notify_all(CHECK);
} else {
assert(h_init_lock() != nullptr, "The initialization state should never be set twice");
set_init_thread(nullptr); // reset _init_thread before changing _init_state
set_init_state(state);
}
ml.notify_all();
}
// Update hierarchy. This is done before the new klass has been added to the SystemDictionary. The Compile_lock
// is grabbed, to ensure that the compiler is not using the class hierarchy.
void InstanceKlass::add_to_hierarchy(JavaThread* current) {
void InstanceKlass::add_to_hierarchy_impl(JavaThread* current) {
assert(!SafepointSynchronize::is_at_safepoint(), "must NOT be at safepoint");
// In case we are not using CHA based vtables we need to make sure the loaded
// deopt is completed before anyone links this class.
// Linking is done with _init_monitor held, by loading and deopting with it
// held we make sure the deopt is completed before linking.
if (!UseVtableBasedCHA) {
init_monitor()->lock();
}
DeoptimizationScope deopt_scope;
{
MutexLocker ml(current, Compile_lock);
@ -1316,12 +1288,26 @@ void InstanceKlass::add_to_hierarchy(JavaThread* current) {
}
// Perform the deopt handshake outside Compile_lock.
deopt_scope.deoptimize_marked();
}
if (!UseVtableBasedCHA) {
init_monitor()->unlock();
void InstanceKlass::add_to_hierarchy(JavaThread* current) {
if (UseVtableBasedCHA || !Universe::is_fully_initialized()) {
add_to_hierarchy_impl(current);
} else {
// In case we are not using CHA based vtables we need to make sure the loaded
// deopt is completed before anyone links this class.
// Linking is done with init_lock held, by loading and deopting with it
// held we make sure the deopt is completed before linking.
Handle h_init_lock(current, init_lock());
ObjectLocker ol(h_init_lock, current);
add_to_hierarchy_impl(current);
// This doesn't need a notify because the wait is only on the class initialization path.
}
}
InstanceKlass* InstanceKlass::implementor() const {
InstanceKlass* volatile* ik = adr_implementor();
if (ik == nullptr) {
@ -2590,7 +2576,6 @@ void InstanceKlass::remove_unshareable_info() {
_nest_host = nullptr;
init_shared_package_entry();
_dep_context_last_cleaned = 0;
_init_monitor = nullptr;
remove_unshareable_flags();
}
@ -2694,9 +2679,6 @@ void InstanceKlass::restore_unshareable_info(ClassLoaderData* loader_data, Handl
if (DiagnoseSyncOnValueBasedClasses && has_value_based_class_annotation()) {
set_is_value_based();
}
// restore the monitor
_init_monitor = create_init_monitor("InstanceKlassInitMonitorRestored_lock");
}
// Check if a class or any of its supertypes has a version older than 50.
@ -2792,9 +2774,6 @@ void InstanceKlass::release_C_heap_structures(bool release_sub_metadata) {
methods_do(method_release_C_heap_structures);
}
// Destroy the init_monitor
delete _init_monitor;
// Deallocate oop map cache
if (_oop_map_cache != nullptr) {
delete _oop_map_cache;
@ -3486,7 +3465,7 @@ nmethod* InstanceKlass::lookup_osr_nmethod(const Method* m, int bci, int comp_le
#define BULLET " - "
static const char* state_names[] = {
"allocated", "loaded", "being_linked", "linked", "being_initialized", "fully_initialized", "initialization_error"
"allocated", "loaded", "linked", "being_initialized", "fully_initialized", "initialization_error"
};
static void print_vtable(intptr_t* start, int len, outputStream* st) {
@ -4136,17 +4115,13 @@ void JNIid::verify(Klass* holder) {
}
void InstanceKlass::set_init_state(ClassState state) {
if (state > loaded) {
assert_lock_strong(_init_monitor);
}
#ifdef ASSERT
bool good_state = is_shared() ? (_init_state <= state)
: (_init_state < state);
bool link_failed = _init_state == being_linked && state == loaded;
assert(good_state || state == allocated || link_failed, "illegal state transition");
assert(good_state || state == allocated, "illegal state transition");
#endif
assert(_init_thread == nullptr, "should be cleared before state change");
Atomic::store(&_init_state, state);
_init_state = state;
}
#if INCLUDE_JVMTI

View File

@ -152,7 +152,6 @@ class InstanceKlass: public Klass {
enum ClassState : u1 {
allocated, // allocated (but not yet linked)
loaded, // loaded and inserted in class hierarchy (but not linked yet)
being_linked, // currently running verifier and rewriter
linked, // successfully linked/verified (but not initialized yet)
being_initialized, // currently running class initializer
fully_initialized, // initialized (successful final state)
@ -226,14 +225,20 @@ class InstanceKlass: public Klass {
volatile u2 _idnum_allocated_count; // JNI/JVMTI: increments with the addition of methods, old ids don't change
// _is_marked_dependent can be set concurrently, thus cannot be part of the
// _misc_flags.
bool _is_marked_dependent; // used for marking during flushing and deoptimization
// Class states are defined as ClassState (see above).
// Place the _init_state here to utilize the unused 2-byte after
// _idnum_allocated_count.
volatile ClassState _init_state; // state of class
u1 _reference_type; // reference type
u1 _reference_type; // reference type
// State is set either at parse time or while executing, atomically to not disturb other state
InstanceKlassFlags _misc_flags;
Monitor* _init_monitor; // mutual exclusion to _init_state and _init_thread.
JavaThread* volatile _init_thread; // Pointer to current thread doing initialization (to handle recursive initialization)
OopMapCache* volatile _oop_map_cache; // OopMapCache for all methods in the klass (allocated lazily)
@ -497,41 +502,23 @@ public:
TRAPS);
JavaThread* init_thread() { return Atomic::load(&_init_thread); }
// We can safely access the name as long as we hold the _init_monitor.
const char* init_thread_name() {
assert(_init_monitor->owned_by_self(), "Must hold _init_monitor here");
return init_thread()->name_raw();
}
public:
// initialization state
bool is_loaded() const { return init_state() >= loaded; }
bool is_linked() const { return init_state() >= linked; }
bool is_being_linked() const { return init_state() == being_linked; }
bool is_initialized() const { return init_state() == fully_initialized; }
bool is_not_initialized() const { return init_state() < being_initialized; }
bool is_being_initialized() const { return init_state() == being_initialized; }
bool is_in_error_state() const { return init_state() == initialization_error; }
bool is_init_thread(JavaThread *thread) { return thread == init_thread(); }
ClassState init_state() const { return Atomic::load(&_init_state); }
bool is_loaded() const { return _init_state >= loaded; }
bool is_linked() const { return _init_state >= linked; }
bool is_initialized() const { return _init_state == fully_initialized; }
bool is_not_initialized() const { return _init_state < being_initialized; }
bool is_being_initialized() const { return _init_state == being_initialized; }
bool is_in_error_state() const { return _init_state == initialization_error; }
bool is_reentrant_initialization(Thread *thread) { return thread == _init_thread; }
ClassState init_state() const { return _init_state; }
const char* init_state_name() const;
bool is_rewritten() const { return _misc_flags.rewritten(); }
class LockLinkState : public StackObj {
InstanceKlass* _ik;
JavaThread* _current;
public:
LockLinkState(InstanceKlass* ik, JavaThread* current) : _ik(ik), _current(current) {
ik->check_link_state_and_wait(current);
}
~LockLinkState() {
if (!_ik->is_linked()) {
// Reset to loaded if linking failed.
_ik->set_initialization_state_and_notify(loaded, _current);
}
}
};
// is this a sealed class
bool is_sealed() const;
@ -829,7 +816,7 @@ public:
// initialization
void call_class_initializer(TRAPS);
void set_initialization_state_and_notify(ClassState state, JavaThread* current);
void set_initialization_state_and_notify(ClassState state, TRAPS);
// OopMapCache support
OopMapCache* oop_map_cache() { return _oop_map_cache; }
@ -841,6 +828,10 @@ public:
void set_jni_ids(JNIid* ids) { _jni_ids = ids; }
JNIid* jni_id_for(int offset);
private:
void add_to_hierarchy_impl(JavaThread* current);
public:
// maintenance of deoptimization dependencies
inline DependencyContext dependencies();
void mark_dependent_nmethods(DeoptimizationScope* deopt_scope, KlassDepChange& changes);
@ -1055,7 +1046,7 @@ public:
public:
u2 idnum_allocated_count() const { return _idnum_allocated_count; }
private:
private:
// initialization state
void set_init_state(ClassState state);
void set_rewritten() { _misc_flags.set_rewritten(true); }
@ -1072,6 +1063,12 @@ public:
jmethodID update_jmethod_id(jmethodID* jmeths, Method* method, int idnum);
public:
// Lock for (1) initialization; (2) access to the ConstantPool of this class.
// Must be one per class and it has to be a VM internal object so java code
// cannot lock it (like the mirror).
// It has to be an object not a Mutex because it's held through java calls.
oop init_lock() const;
// Returns the array class for the n'th dimension
virtual ArrayKlass* array_klass(int n, TRAPS);
virtual ArrayKlass* array_klass_or_null(int n);
@ -1081,10 +1078,9 @@ public:
virtual ArrayKlass* array_klass_or_null();
static void clean_initialization_error_table();
Monitor* init_monitor() const { return _init_monitor; }
private:
void check_link_state_and_wait(JavaThread* current);
void fence_and_clear_init_lock();
bool link_class_impl (TRAPS);
bool verify_code (TRAPS);
void initialize_impl (TRAPS);

View File

@ -1336,7 +1336,7 @@ methodHandle SharedRuntime::resolve_helper(bool is_virtual, bool is_optimized, T
if (invoke_code == Bytecodes::_invokestatic) {
assert(callee_method->method_holder()->is_initialized() ||
callee_method->method_holder()->is_init_thread(current),
callee_method->method_holder()->is_reentrant_initialization(current),
"invalid class initialization state for invoke_static");
if (!VM_Version::supports_fast_class_init_checks() && callee_method->needs_clinit_barrier()) {
// In order to keep class initialization check, do not patch call

View File

@ -821,6 +821,16 @@ int ObjectSynchronizer::wait(Handle obj, jlong millis, TRAPS) {
return ret_code;
}
void ObjectSynchronizer::waitUninterruptibly(Handle obj, jlong millis, TRAPS) {
if (millis < 0) {
THROW_MSG(vmSymbols::java_lang_IllegalArgumentException(), "timeout value is negative");
}
ObjectSynchronizer::inflate(THREAD,
obj(),
inflate_cause_wait)->wait(millis, false, THREAD);
}
void ObjectSynchronizer::notify(Handle obj, TRAPS) {
JavaThread* current = THREAD;

View File

@ -119,6 +119,11 @@ public:
static bool quick_notify(oopDesc* obj, JavaThread* current, bool All);
static bool quick_enter(oop obj, JavaThread* current, BasicLock* Lock);
// Special internal-use-only method for use by JVM infrastructure
// that needs to wait() on a java-level object but that can't risk
// throwing unexpected InterruptedExecutionExceptions.
static void waitUninterruptibly(Handle obj, jlong Millis, TRAPS);
// Inflate light weight monitor to heavy weight monitor
static ObjectMonitor* inflate(Thread* current, oop obj, const InflateCause cause);
// Used to inflate a monitor as if it was done from the thread JavaThread.
@ -225,6 +230,7 @@ class ObjectLocker : public StackObj {
// Monitor behavior
void wait(TRAPS) { ObjectSynchronizer::wait(_obj, 0, CHECK); } // wait forever
void wait_uninterruptibly(TRAPS) { ObjectSynchronizer::waitUninterruptibly(_obj, 0, CHECK); } // wait forever
void notify_all(TRAPS) { ObjectSynchronizer::notifyall(_obj, CHECK); }
};

View File

@ -205,8 +205,9 @@ void javaVFrame::print_lock_info_on(outputStream* st, int frame_count) {
Klass* k = obj->klass();
st->print_cr("\t- %s <" INTPTR_FORMAT "> (a %s)", "parking to wait for ", p2i(obj), k->external_name());
}
else if (thread()->osthread()->get_state() == CONDVAR_WAIT) {
// We are waiting on the native class initialization monitor.
else if (thread()->osthread()->get_state() == OBJECT_WAIT) {
// We are waiting on an Object monitor but Object.wait() isn't the
// top-frame, so we should be waiting on a Class initialization monitor.
InstanceKlass* k = thread()->class_to_be_initialized();
if (k != nullptr) {
st->print_cr("\t- waiting on the Class initialization monitor for %s", k->external_name());

View File

@ -246,6 +246,7 @@
nonstatic_field(InstanceKlass, _nonstatic_oop_map_size, int) \
volatile_nonstatic_field(InstanceKlass, _init_state, InstanceKlass::ClassState) \
volatile_nonstatic_field(InstanceKlass, _init_thread, JavaThread*) \
nonstatic_field(InstanceKlass, _is_marked_dependent, bool) \
nonstatic_field(InstanceKlass, _itable_len, int) \
nonstatic_field(InstanceKlass, _nest_host_index, u2) \
nonstatic_field(InstanceKlass, _reference_type, u1) \
@ -2163,7 +2164,6 @@
\
declare_constant(InstanceKlass::allocated) \
declare_constant(InstanceKlass::loaded) \
declare_constant(InstanceKlass::being_linked) \
declare_constant(InstanceKlass::linked) \
declare_constant(InstanceKlass::being_initialized) \
declare_constant(InstanceKlass::fully_initialized) \

View File

@ -1089,6 +1089,14 @@ u4 DumperSupport::get_static_fields_size(InstanceKlass* ik, u2& field_count) {
}
}
// Also provide a pointer to the init_lock if present, so there aren't unreferenced int[0]
// arrays.
oop init_lock = ik->init_lock();
if (init_lock != nullptr) {
field_count++;
size += sizeof(address);
}
// We write the value itself plus a name and a one byte type tag per field.
return checked_cast<u4>(size + field_count * (sizeof(address) + 1));
}
@ -1126,6 +1134,14 @@ void DumperSupport::dump_static_fields(AbstractDumpWriter* writer, Klass* k) {
prev = prev->previous_versions();
}
}
// Add init lock to the end if the class is not yet initialized
oop init_lock = ik->init_lock();
if (init_lock != nullptr) {
writer->write_symbolID(vmSymbols::init_lock_name()); // name
writer->write_u1(sig2tag(vmSymbols::int_array_signature())); // type
writer->write_objectID(init_lock);
}
}
// dump the raw values of the instance fields of the given object

View File

@ -57,7 +57,6 @@ public class InstanceKlass extends Klass {
// ClassState constants
private static int CLASS_STATE_ALLOCATED;
private static int CLASS_STATE_LOADED;
private static int CLASS_STATE_BEING_LINKED;
private static int CLASS_STATE_LINKED;
private static int CLASS_STATE_BEING_INITIALIZED;
private static int CLASS_STATE_FULLY_INITIALIZED;
@ -101,7 +100,6 @@ public class InstanceKlass extends Klass {
// read ClassState constants
CLASS_STATE_ALLOCATED = db.lookupIntConstant("InstanceKlass::allocated").intValue();
CLASS_STATE_LOADED = db.lookupIntConstant("InstanceKlass::loaded").intValue();
CLASS_STATE_BEING_LINKED = db.lookupIntConstant("InstanceKlass::being_linked").intValue();
CLASS_STATE_LINKED = db.lookupIntConstant("InstanceKlass::linked").intValue();
CLASS_STATE_BEING_INITIALIZED = db.lookupIntConstant("InstanceKlass::being_initialized").intValue();
CLASS_STATE_FULLY_INITIALIZED = db.lookupIntConstant("InstanceKlass::fully_initialized").intValue();
@ -158,7 +156,6 @@ public class InstanceKlass extends Klass {
public static class ClassState {
public static final ClassState ALLOCATED = new ClassState("allocated");
public static final ClassState LOADED = new ClassState("loaded");
public static final ClassState BEING_LINKED = new ClassState("beingLinked");
public static final ClassState LINKED = new ClassState("linked");
public static final ClassState BEING_INITIALIZED = new ClassState("beingInitialized");
public static final ClassState FULLY_INITIALIZED = new ClassState("fullyInitialized");
@ -182,8 +179,6 @@ public class InstanceKlass extends Klass {
return ClassState.ALLOCATED;
} else if (state == CLASS_STATE_LOADED) {
return ClassState.LOADED;
} else if (state == CLASS_STATE_BEING_LINKED) {
return ClassState.BEING_LINKED;
} else if (state == CLASS_STATE_LINKED) {
return ClassState.LINKED;
} else if (state == CLASS_STATE_BEING_INITIALIZED) {

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2019, 2022, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -60,8 +60,7 @@ public class TestThreadDumpClassInitMonitor {
*/
final static String TEST_THREAD = "TestThread";
final static String TEST_THREAD_ENTRY = "\"" + TEST_THREAD;
// final static String IN_OBJECT_WAIT = "in Object.wait()";
final static String IN_CONVAR_WAIT = "waiting on condition";
final static String IN_OBJECT_WAIT = "in Object.wait()";
final static String THREAD_STATE = "java.lang.Thread.State: RUNNABLE";
final static String THREAD_INFO = "Thread:"; // the details are not important
final static String JAVATHREAD_STATE = "JavaThread state: _thread_blocked";
@ -140,7 +139,7 @@ public class TestThreadDumpClassInitMonitor {
continue;
}
foundLines++;
if (!line.contains(IN_CONVAR_WAIT)) {
if (!line.contains(IN_OBJECT_WAIT)) {
throw new Error("Unexpected initial stack line: " + line);
}
continue;

View File

@ -0,0 +1,156 @@
/*
* Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/**
* @test
* @bug 8333542
* @summary Missed breakpoint due to JVM not blocking other threads while
* delivering a ClassPrepareEvent.
*
* @run build TestScaffold VMConnection TargetListener TargetAdapter
* @run compile -g BreakpointOnClassPrepare.java
* @run driver BreakpointOnClassPrepare SUSPEND_NONE
* @run driver BreakpointOnClassPrepare SUSPEND_EVENT_THREAD
* @run driver BreakpointOnClassPrepare SUSPEND_ALL
*/
import com.sun.jdi.*;
import com.sun.jdi.event.*;
import com.sun.jdi.request.*;
import java.util.*;
// The debuggee spawns 50 threads that call LoadedClass.foo(). The debugger enables
// ClassPrepareEvent for LoadedClass, and sets a breakpoint on LoadedClass.foo() when
// the ClassPrepareEvent arrives. The debugger expects 50 breakpoints to be hit.
// This verifies that the thread that causes the generation of the ClassPrepareEvent
// has properly blocked all other threads from executing LoadedClass.foo() until the
// ClassPrepareEvent has been delivered.
class LoadedClass {
static void foo(int k) {
System.out.println("HIT = " + k); // set breakpoint here
}
}
class BreakpointOnClassPrepareTarg {
public static void main(String[] args) throws InterruptedException {
System.out.println("Start");
Thread threads[] = new Thread[BreakpointOnClassPrepare.NUM_BREAKPOINTS];
for (int i = 0; i < BreakpointOnClassPrepare.NUM_BREAKPOINTS; i++) {
int k = i;
Thread t = DebuggeeWrapper.newThread(() -> {
System.out.println("k = " + k);
LoadedClass.foo(k);
});
threads[i] = t;
t.setDaemon(true);
t.setName("MyThread-" + k);
t.start();
}
for (int i = 0; i < BreakpointOnClassPrepare.NUM_BREAKPOINTS; i++) {
try {
Thread t = threads[i];
t.join();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
System.out.println("Finish");
}
}
/********** test program **********/
public class BreakpointOnClassPrepare extends TestScaffold {
ClassType targetClass;
ThreadReference mainThread;
BreakpointOnClassPrepare(String args[]) {
super(args);
}
public static void main(String[] args) throws Exception {
new BreakpointOnClassPrepare(args).startTests();
}
/********** event handlers **********/
static final int NUM_BREAKPOINTS = 50;
int bkptCount;
BreakpointRequest bkptRequest;
public void breakpointReached(BreakpointEvent event) {
bkptCount++;
System.out.println("Got BreakpointEvent: " + bkptCount + " for thread " + event.thread());
}
public void vmDisconnected(VMDisconnectEvent event) {
println("Got VMDisconnectEvent");
}
/********** test core **********/
protected void runTests() throws Exception {
/* Determine which suspend policy to use. */
int policy;
if (args.length != 1) {
throw new RuntimeException("Invalid number of args: " + args.length);
}
String policyString = args[0];
if (policyString.equals("SUSPEND_NONE")) {
policy = EventRequest.SUSPEND_NONE;
} else if (policyString.equals("SUSPEND_ALL")) {
policy = EventRequest.SUSPEND_ALL;
} else if (policyString.equals("SUSPEND_EVENT_THREAD")) {
policy = EventRequest.SUSPEND_EVENT_THREAD;
} else {
throw new RuntimeException("Invalid suspend policy: " + policyString);
}
/* Stop when the target is loaded. */
BreakpointEvent bpe = startToMain("BreakpointOnClassPrepareTarg");
/* Stop when "LoadedClass" is loaded. */
EventRequestManager erm = vm().eventRequestManager();
ClassPrepareEvent cpe = resumeToPrepareOf("LoadedClass");
println("Got ClassPrepareEvent: " + cpe);
/* Set a breakpoint for each time LoadedClass.foo() is called. */
ClassType loadedClass = (ClassType)cpe.referenceType() ;
Location loc1 = findMethodLocation(loadedClass, "foo", "(I)V", 1);
bkptRequest = erm.createBreakpointRequest(loc1);
bkptRequest.setSuspendPolicy(policy);
bkptRequest.enable();
listenUntilVMDisconnect();
if (!testFailed && bkptCount == NUM_BREAKPOINTS) {
println("BreakpointOnClassPrepare: passed");
} else {
throw new Exception("BreakpointOnClassPrepare: failed. bkptCount == " + bkptCount);
}
}
}