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:
parent
86b0cf259f
commit
b3bf31a0a0
@ -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);
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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;") \
|
||||
|
@ -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.
|
||||
|
@ -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());
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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); }
|
||||
};
|
||||
|
||||
|
@ -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());
|
||||
|
@ -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) \
|
||||
|
@ -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
|
||||
|
@ -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) {
|
||||
|
@ -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;
|
||||
|
156
test/jdk/com/sun/jdi/BreakpointOnClassPrepare.java
Normal file
156
test/jdk/com/sun/jdi/BreakpointOnClassPrepare.java
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user