8079205: CallSite dependency tracking is broken after sun.misc.Cleaner became automatically cleared

Reviewed-by: roland, psandoz, plevart, kbarrett, jrose
This commit is contained in:
Vladimir Ivanov 2015-05-15 19:23:11 +03:00
parent 18f7135255
commit e5a92a9fb9
18 changed files with 320 additions and 256 deletions

View File

@ -49,25 +49,6 @@ ciMethodHandle* ciCallSite::get_target() const {
return CURRENT_ENV->get_object(method_handle_oop)->as_method_handle();
}
// ------------------------------------------------------------------
// ciCallSite::get_context
//
// Return the target MethodHandle of this CallSite.
ciKlass* ciCallSite::get_context() {
assert(!is_constant_call_site(), "");
VM_ENTRY_MARK;
oop call_site_oop = get_oop();
InstanceKlass* ctxk = MethodHandles::get_call_site_context(call_site_oop);
if (ctxk == NULL) {
// The call site doesn't have a context associated. Set it to the default context.
oop def_context_oop = java_lang_invoke_CallSite::default_context();
java_lang_invoke_CallSite::set_context_cas(call_site_oop, def_context_oop, /*expected=*/NULL);
ctxk = MethodHandles::get_call_site_context(call_site_oop);
}
return (CURRENT_ENV->get_metadata(ctxk))->as_klass();
}
// ------------------------------------------------------------------
// ciCallSite::print
//

View File

@ -43,7 +43,6 @@ public:
// Return the target MethodHandle of this CallSite.
ciMethodHandle* get_target() const;
ciKlass* get_context();
void print();
};

View File

@ -453,8 +453,12 @@ int ciInstanceKlass::compute_nonstatic_fields() {
if (fields == NULL) {
// This can happen if this class (java.lang.Class) has invisible fields.
_nonstatic_fields = super_fields;
return super_fields->length();
if (super_fields != NULL) {
_nonstatic_fields = super_fields;
return super_fields->length();
} else {
return 0;
}
}
int flen = fields->length();

View File

@ -2967,47 +2967,42 @@ int java_lang_invoke_MethodType::rtype_slot_count(oop mt) {
int java_lang_invoke_CallSite::_target_offset;
int java_lang_invoke_CallSite::_context_offset;
int java_lang_invoke_CallSite::_default_context_offset;
void java_lang_invoke_CallSite::compute_offsets() {
Klass* k = SystemDictionary::CallSite_klass();
if (k != NULL) {
compute_offset(_target_offset, k, vmSymbols::target_name(), vmSymbols::java_lang_invoke_MethodHandle_signature());
compute_offset(_context_offset, k, vmSymbols::context_name(), vmSymbols::sun_misc_Cleaner_signature());
compute_offset(_default_context_offset, k,
vmSymbols::DEFAULT_CONTEXT_name(), vmSymbols::sun_misc_Cleaner_signature(),
/*is_static=*/true, /*allow_super=*/false);
compute_offset(_context_offset, k, vmSymbols::context_name(),
vmSymbols::java_lang_invoke_MethodHandleNatives_CallSiteContext_signature());
}
}
oop java_lang_invoke_CallSite::context_volatile(oop call_site) {
oop java_lang_invoke_CallSite::context(oop call_site) {
assert(java_lang_invoke_CallSite::is_instance(call_site), "");
oop dep_oop = call_site->obj_field_volatile(_context_offset);
oop dep_oop = call_site->obj_field(_context_offset);
return dep_oop;
}
void java_lang_invoke_CallSite::set_context_volatile(oop call_site, oop context) {
assert(java_lang_invoke_CallSite::is_instance(call_site), "");
call_site->obj_field_put_volatile(_context_offset, context);
}
// Support for java_lang_invoke_MethodHandleNatives_CallSiteContext
bool java_lang_invoke_CallSite::set_context_cas(oop call_site, oop context, oop expected) {
assert(java_lang_invoke_CallSite::is_instance(call_site), "");
HeapWord* context_addr = call_site->obj_field_addr<HeapWord>(_context_offset);
oop res = oopDesc::atomic_compare_exchange_oop(context, context_addr, expected, true);
bool success = (res == expected);
if (success) {
update_barrier_set((void*)context_addr, context);
int java_lang_invoke_MethodHandleNatives_CallSiteContext::_vmdependencies_offset;
void java_lang_invoke_MethodHandleNatives_CallSiteContext::compute_offsets() {
Klass* k = SystemDictionary::Context_klass();
if (k != NULL) {
CALLSITECONTEXT_INJECTED_FIELDS(INJECTED_FIELD_COMPUTE_OFFSET);
}
return success;
}
oop java_lang_invoke_CallSite::default_context() {
InstanceKlass* ik = InstanceKlass::cast(SystemDictionary::CallSite_klass());
oop def_context_oop = ik->java_mirror()->obj_field(_default_context_offset);
assert(!oopDesc::is_null(def_context_oop), "");
return def_context_oop;
nmethodBucket* java_lang_invoke_MethodHandleNatives_CallSiteContext::vmdependencies(oop call_site) {
assert(java_lang_invoke_MethodHandleNatives_CallSiteContext::is_instance(call_site), "");
return (nmethodBucket*) (address) call_site->long_field(_vmdependencies_offset);
}
void java_lang_invoke_MethodHandleNatives_CallSiteContext::set_vmdependencies(oop call_site, nmethodBucket* context) {
assert(java_lang_invoke_MethodHandleNatives_CallSiteContext::is_instance(call_site), "");
call_site->long_field_put(_vmdependencies_offset, (jlong) (address) context);
}
// Support for java_security_AccessControlContext
@ -3403,6 +3398,7 @@ void JavaClasses::compute_offsets() {
java_lang_invoke_LambdaForm::compute_offsets();
java_lang_invoke_MethodType::compute_offsets();
java_lang_invoke_CallSite::compute_offsets();
java_lang_invoke_MethodHandleNatives_CallSiteContext::compute_offsets();
java_security_AccessControlContext::compute_offsets();
// Initialize reflection classes. The layouts of these classes
// changed with the new reflection implementation in JDK 1.4, and

View File

@ -1170,8 +1170,6 @@ class java_lang_invoke_CallSite: AllStatic {
private:
static int _target_offset;
static int _context_offset;
static int _default_context_offset;
static void compute_offsets();
@ -1181,11 +1179,7 @@ public:
static void set_target( oop site, oop target);
static void set_target_volatile( oop site, oop target);
static oop context_volatile(oop site);
static void set_context_volatile(oop site, oop context);
static bool set_context_cas (oop site, oop context, oop expected);
static oop default_context();
static oop context(oop site);
// Testers
static bool is_subclass(Klass* klass) {
@ -1197,6 +1191,31 @@ public:
static int target_offset_in_bytes() { return _target_offset; }
};
// Interface to java.lang.invoke.MethodHandleNatives$CallSiteContext objects
#define CALLSITECONTEXT_INJECTED_FIELDS(macro) \
macro(java_lang_invoke_MethodHandleNatives_CallSiteContext, vmdependencies, intptr_signature, false)
class java_lang_invoke_MethodHandleNatives_CallSiteContext : AllStatic {
friend class JavaClasses;
private:
static int _vmdependencies_offset;
static void compute_offsets();
public:
// Accessors
static nmethodBucket* vmdependencies(oop context);
static void set_vmdependencies(oop context, nmethodBucket* bucket);
// Testers
static bool is_subclass(Klass* klass) {
return klass->is_subclass_of(SystemDictionary::Context_klass());
}
static bool is_instance(oop obj);
};
// Interface to java.security.AccessControlContext objects
class java_security_AccessControlContext: AllStatic {
@ -1406,7 +1425,8 @@ class InjectedField {
#define ALL_INJECTED_FIELDS(macro) \
CLASS_INJECTED_FIELDS(macro) \
CLASSLOADER_INJECTED_FIELDS(macro) \
MEMBERNAME_INJECTED_FIELDS(macro)
MEMBERNAME_INJECTED_FIELDS(macro) \
CALLSITECONTEXT_INJECTED_FIELDS(macro)
// Interface to hard-coded offset checking

View File

@ -49,6 +49,10 @@ inline bool java_lang_invoke_CallSite::is_instance(oop obj) {
return obj != NULL && is_subclass(obj->klass());
}
inline bool java_lang_invoke_MethodHandleNatives_CallSiteContext::is_instance(oop obj) {
return obj != NULL && is_subclass(obj->klass());
}
inline bool java_lang_invoke_MemberName::is_instance(oop obj) {
return obj != NULL && is_subclass(obj->klass());
}

View File

@ -159,6 +159,7 @@ class Ticks;
do_klass(MethodType_klass, java_lang_invoke_MethodType, Pre ) \
do_klass(BootstrapMethodError_klass, java_lang_BootstrapMethodError, Pre ) \
do_klass(CallSite_klass, java_lang_invoke_CallSite, Pre ) \
do_klass(Context_klass, java_lang_invoke_MethodHandleNatives_CallSiteContext, Pre ) \
do_klass(ConstantCallSite_klass, java_lang_invoke_ConstantCallSite, Pre ) \
do_klass(MutableCallSite_klass, java_lang_invoke_MutableCallSite, Pre ) \
do_klass(VolatileCallSite_klass, java_lang_invoke_VolatileCallSite, Pre ) \

View File

@ -274,12 +274,14 @@
/* internal classes known only to the JVM: */ \
template(java_lang_invoke_MemberName, "java/lang/invoke/MemberName") \
template(java_lang_invoke_MethodHandleNatives, "java/lang/invoke/MethodHandleNatives") \
template(java_lang_invoke_MethodHandleNatives_CallSiteContext, "java/lang/invoke/MethodHandleNatives$CallSiteContext") \
template(java_lang_invoke_LambdaForm, "java/lang/invoke/LambdaForm") \
template(java_lang_invoke_ForceInline_signature, "Ljava/lang/invoke/ForceInline;") \
template(java_lang_invoke_DontInline_signature, "Ljava/lang/invoke/DontInline;") \
template(java_lang_invoke_Stable_signature, "Ljava/lang/invoke/Stable;") \
template(java_lang_invoke_LambdaForm_Compiled_signature, "Ljava/lang/invoke/LambdaForm$Compiled;") \
template(java_lang_invoke_LambdaForm_Hidden_signature, "Ljava/lang/invoke/LambdaForm$Hidden;") \
template(java_lang_invoke_MethodHandleNatives_CallSiteContext_signature, "Ljava/lang/invoke/MethodHandleNatives$CallSiteContext;") \
/* internal up-calls made only by the JVM, via class sun.invoke.MethodHandleNatives: */ \
template(findMethodHandleType_name, "findMethodHandleType") \
template(findMethodHandleType_signature, "(Ljava/lang/Class;[Ljava/lang/Class;)Ljava/lang/invoke/MethodType;") \
@ -401,7 +403,7 @@
template(protection_domain_name, "protection_domain") \
template(signers_name, "signers_name") \
template(loader_data_name, "loader_data") \
template(dependencies_name, "dependencies") \
template(vmdependencies_name, "vmdependencies") \
template(input_stream_void_signature, "(Ljava/io/InputStream;)V") \
template(getFileURL_name, "getFileURL") \
template(getFileURL_signature, "(Ljava/io/File;)Ljava/net/URL;") \

View File

@ -1047,40 +1047,6 @@ void CodeCache::flush_dependents_on(instanceKlassHandle dependee) {
}
}
// Flushes compiled methods dependent on a particular CallSite
// instance when its target is different than the given MethodHandle.
void CodeCache::flush_dependents_on(Handle call_site, Handle method_handle) {
assert_lock_strong(Compile_lock);
if (number_of_nmethods_with_dependencies() == 0) return;
// CodeCache can only be updated by a thread_in_VM and they will all be
// stopped during the safepoint so CodeCache will be safe to update without
// holding the CodeCache_lock.
CallSiteDepChange changes(call_site(), method_handle());
// Compute the dependent nmethods that have a reference to a
// CallSite object. We use InstanceKlass::mark_dependent_nmethod
// directly instead of CodeCache::mark_for_deoptimization because we
// want dependents on the call site class only not all classes in
// the ContextStream.
int marked = 0;
{
MutexLockerEx mu(CodeCache_lock, Mutex::_no_safepoint_check_flag);
InstanceKlass* ctxk = MethodHandles::get_call_site_context(call_site());
if (ctxk == NULL) {
return; // No dependencies to invalidate yet.
}
marked = ctxk->mark_dependent_nmethods(changes);
}
if (marked > 0) {
// At least one nmethod has been marked for deoptimization
VM_Deoptimize op;
VMThread::execute(&op);
}
}
#ifdef HOTSWAP
// Flushes compiled methods dependent on dependee in the evolutionary sense
void CodeCache::flush_evol_dependents_on(instanceKlassHandle ev_k_h) {

View File

@ -224,7 +224,6 @@ class CodeCache : AllStatic {
// Flushing and deoptimization
static void flush_dependents_on(instanceKlassHandle dependee);
static void flush_dependents_on(Handle call_site, Handle method_handle);
#ifdef HOTSWAP
// Flushing and deoptimization in case of evolution
static void flush_evol_dependents_on(instanceKlassHandle dependee);

View File

@ -117,9 +117,7 @@ void Dependencies::assert_has_no_finalizable_subclasses(ciKlass* ctxk) {
}
void Dependencies::assert_call_site_target_value(ciCallSite* call_site, ciMethodHandle* method_handle) {
ciKlass* ctxk = call_site->get_context();
check_ctxk(ctxk);
assert_common_3(call_site_target_value, ctxk, call_site, method_handle);
assert_common_2(call_site_target_value, call_site, method_handle);
}
// Helper function. If we are adding a new dep. under ctxk2,
@ -175,7 +173,6 @@ void Dependencies::assert_common_2(DepType dept,
}
}
} else {
assert(dep_implicit_context_arg(dept) == 0, "sanity");
if (note_dep_seen(dept, x0) && note_dep_seen(dept, x1)) {
// look in this bucket for redundant assertions
const int stride = 2;
@ -389,7 +386,7 @@ int Dependencies::_dep_args[TYPE_LIMIT] = {
3, // unique_concrete_subtypes_2 ctxk, k1, k2
3, // unique_concrete_methods_2 ctxk, m1, m2
1, // no_finalizable_subclasses ctxk
3 // call_site_target_value ctxk, call_site, method_handle
2 // call_site_target_value call_site, method_handle
};
const char* Dependencies::dep_name(Dependencies::DepType dept) {
@ -1515,16 +1512,11 @@ Klass* Dependencies::check_has_no_finalizable_subclasses(Klass* ctxk, KlassDepCh
return find_finalizable_subclass(search_at);
}
Klass* Dependencies::check_call_site_target_value(Klass* recorded_ctxk, oop call_site, oop method_handle, CallSiteDepChange* changes) {
assert(call_site->is_a(SystemDictionary::CallSite_klass()), "sanity");
Klass* Dependencies::check_call_site_target_value(oop call_site, oop method_handle, CallSiteDepChange* changes) {
assert(!oopDesc::is_null(call_site), "sanity");
assert(!oopDesc::is_null(method_handle), "sanity");
assert(call_site->is_a(SystemDictionary::CallSite_klass()), "sanity");
Klass* call_site_ctxk = MethodHandles::get_call_site_context(call_site);
assert(!Klass::is_null(call_site_ctxk), "call site context should be initialized already");
if (recorded_ctxk != call_site_ctxk) {
// Stale context
return recorded_ctxk;
}
if (changes == NULL) {
// Validate all CallSites
if (java_lang_invoke_CallSite::target(call_site) != method_handle)
@ -1599,7 +1591,7 @@ Klass* Dependencies::DepStream::check_call_site_dependency(CallSiteDepChange* ch
Klass* witness = NULL;
switch (type()) {
case call_site_target_value:
witness = check_call_site_target_value(context_type(), argument_oop(1), argument_oop(2), changes);
witness = check_call_site_target_value(argument_oop(0), argument_oop(1), changes);
break;
default:
witness = NULL;

View File

@ -173,7 +173,7 @@ class Dependencies: public ResourceObj {
non_klass_types = (1 << call_site_target_value),
klass_types = all_types & ~non_klass_types,
non_ctxk_types = (1 << evol_method),
non_ctxk_types = (1 << evol_method) | (1 << call_site_target_value),
implicit_ctxk_types = 0,
explicit_ctxk_types = all_types & ~(non_ctxk_types | implicit_ctxk_types),
@ -330,7 +330,7 @@ class Dependencies: public ResourceObj {
static Klass* check_exclusive_concrete_methods(Klass* ctxk, Method* m1, Method* m2,
KlassDepChange* changes = NULL);
static Klass* check_has_no_finalizable_subclasses(Klass* ctxk, KlassDepChange* changes = NULL);
static Klass* check_call_site_target_value(Klass* recorded_ctxk, oop call_site, oop method_handle, CallSiteDepChange* changes = NULL);
static Klass* check_call_site_target_value(oop call_site, oop method_handle, CallSiteDepChange* changes = NULL);
// A returned Klass* is NULL if the dependency assertion is still
// valid. A non-NULL Klass* is a 'witness' to the assertion
// failure, a point in the class hierarchy where the assertion has
@ -496,7 +496,7 @@ class Dependencies: public ResourceObj {
bool next();
DepType type() { return _type; }
bool is_oop_argument(int i) { return type() == call_site_target_value && i > 0; }
bool is_oop_argument(int i) { return type() == call_site_target_value; }
uintptr_t get_identifier(int i);
int argument_count() { return dep_args(type()); }

View File

@ -565,13 +565,18 @@ nmethod* nmethod::new_nmethod(methodHandle method,
// the number of methods compiled. For applications with a lot
// classes the slow way is too slow.
for (Dependencies::DepStream deps(nm); deps.next(); ) {
Klass* klass = deps.context_type();
if (klass == NULL) {
continue; // ignore things like evol_method
if (deps.type() == Dependencies::call_site_target_value) {
// CallSite dependencies are managed on per-CallSite instance basis.
oop call_site = deps.argument_oop(0);
MethodHandles::add_dependent_nmethod(call_site, nm);
} else {
Klass* klass = deps.context_type();
if (klass == NULL) {
continue; // ignore things like evol_method
}
// record this nmethod as dependent on this klass
InstanceKlass::cast(klass)->add_dependent_nmethod(nm);
}
// record this nmethod as dependent on this klass
InstanceKlass::cast(klass)->add_dependent_nmethod(nm);
}
NOT_PRODUCT(nmethod_stats.note_nmethod(nm));
if (PrintAssembly || CompilerOracle::has_option_string(method, "PrintAssembly")) {
@ -1464,13 +1469,20 @@ void nmethod::flush_dependencies(BoolObjectClosure* is_alive) {
if (!has_flushed_dependencies()) {
set_has_flushed_dependencies();
for (Dependencies::DepStream deps(this); deps.next(); ) {
Klass* klass = deps.context_type();
if (klass == NULL) continue; // ignore things like evol_method
// During GC the is_alive closure is non-NULL, and is used to
// determine liveness of dependees that need to be updated.
if (is_alive == NULL || klass->is_loader_alive(is_alive)) {
InstanceKlass::cast(klass)->remove_dependent_nmethod(this);
if (deps.type() == Dependencies::call_site_target_value) {
// CallSite dependencies are managed on per-CallSite instance basis.
oop call_site = deps.argument_oop(0);
MethodHandles::remove_dependent_nmethod(call_site, this);
} else {
Klass* klass = deps.context_type();
if (klass == NULL) {
continue; // ignore things like evol_method
}
// During GC the is_alive closure is non-NULL, and is used to
// determine liveness of dependees that need to be updated.
if (is_alive == NULL || klass->is_loader_alive(is_alive)) {
InstanceKlass::cast(klass)->remove_dependent_nmethod(this);
}
}
}
}

View File

@ -1830,11 +1830,10 @@ int nmethodBucket::decrement() {
// are dependent on the changes that were passed in and mark them for
// deoptimization. Returns the number of nmethods found.
//
int InstanceKlass::mark_dependent_nmethods(DepChange& changes) {
int nmethodBucket::mark_dependent_nmethods(nmethodBucket* deps, DepChange& changes) {
assert_locked_or_safepoint(CodeCache_lock);
int found = 0;
nmethodBucket* b = _dependencies;
while (b != NULL) {
for (nmethodBucket* b = deps; b != NULL; b = b->next()) {
nmethod* nm = b->get_nmethod();
// since dependencies aren't removed until an nmethod becomes a zombie,
// the dependency list may contain nmethods which aren't alive.
@ -1842,7 +1841,6 @@ int InstanceKlass::mark_dependent_nmethods(DepChange& changes) {
if (TraceDependencies) {
ResourceMark rm;
tty->print_cr("Marked for deoptimization");
tty->print_cr(" context = %s", this->external_name());
changes.print();
nm->print();
nm->print_dependencies();
@ -1850,36 +1848,119 @@ int InstanceKlass::mark_dependent_nmethods(DepChange& changes) {
nm->mark_for_deoptimization();
found++;
}
b = b->next();
}
return found;
}
//
// Add an nmethodBucket to the list of dependencies for this nmethod.
// It's possible that an nmethod has multiple dependencies on this klass
// so a count is kept for each bucket to guarantee that creation and
// deletion of dependencies is consistent. Returns new head of the list.
//
nmethodBucket* nmethodBucket::add_dependent_nmethod(nmethodBucket* deps, nmethod* nm) {
assert_locked_or_safepoint(CodeCache_lock);
for (nmethodBucket* b = deps; b != NULL; b = b->next()) {
if (nm == b->get_nmethod()) {
b->increment();
return deps;
}
}
return new nmethodBucket(nm, deps);
}
//
// Decrement count of the nmethod in the dependency list and remove
// the bucket completely when the count goes to 0. This method must
// find a corresponding bucket otherwise there's a bug in the
// recording of dependencies. Returns true if the bucket is ready for reclamation.
//
bool nmethodBucket::remove_dependent_nmethod(nmethodBucket* deps, nmethod* nm) {
assert_locked_or_safepoint(CodeCache_lock);
for (nmethodBucket* b = deps; b != NULL; b = b->next()) {
if (nm == b->get_nmethod()) {
int val = b->decrement();
guarantee(val >= 0, err_msg("Underflow: %d", val));
return (val == 0);
}
}
#ifdef ASSERT
tty->print_raw_cr("### can't find dependent nmethod");
nm->print();
#endif // ASSERT
ShouldNotReachHere();
return false;
}
//
// Reclaim all unused buckets. Returns new head of the list.
//
nmethodBucket* nmethodBucket::clean_dependent_nmethods(nmethodBucket* deps) {
nmethodBucket* first = deps;
nmethodBucket* last = NULL;
nmethodBucket* b = first;
while (b != NULL) {
assert(b->count() >= 0, err_msg("bucket count: %d", b->count()));
nmethodBucket* next = b->next();
if (b->count() == 0) {
if (last == NULL) {
first = next;
} else {
last->set_next(next);
}
delete b;
// last stays the same.
} else {
last = b;
}
b = next;
}
return first;
}
#ifndef PRODUCT
void nmethodBucket::print_dependent_nmethods(nmethodBucket* deps, bool verbose) {
int idx = 0;
for (nmethodBucket* b = deps; b != NULL; b = b->next()) {
nmethod* nm = b->get_nmethod();
tty->print("[%d] count=%d { ", idx++, b->count());
if (!verbose) {
nm->print_on(tty, "nmethod");
tty->print_cr(" } ");
} else {
nm->print();
nm->print_dependencies();
tty->print_cr("--- } ");
}
}
}
bool nmethodBucket::is_dependent_nmethod(nmethodBucket* deps, nmethod* nm) {
for (nmethodBucket* b = deps; b != NULL; b = b->next()) {
if (nm == b->get_nmethod()) {
#ifdef ASSERT
int count = b->count();
assert(count >= 0, err_msg("count shouldn't be negative: %d", count));
#endif
return true;
}
}
return false;
}
#endif //PRODUCT
int InstanceKlass::mark_dependent_nmethods(DepChange& changes) {
assert_locked_or_safepoint(CodeCache_lock);
return nmethodBucket::mark_dependent_nmethods(_dependencies, changes);
}
void InstanceKlass::clean_dependent_nmethods() {
assert_locked_or_safepoint(CodeCache_lock);
if (has_unloaded_dependent()) {
nmethodBucket* b = _dependencies;
nmethodBucket* last = NULL;
while (b != NULL) {
assert(b->count() >= 0, err_msg("bucket count: %d", b->count()));
nmethodBucket* next = b->next();
if (b->count() == 0) {
if (last == NULL) {
_dependencies = next;
} else {
last->set_next(next);
}
delete b;
// last stays the same.
} else {
last = b;
}
b = next;
}
_dependencies = nmethodBucket::clean_dependent_nmethods(_dependencies);
set_has_unloaded_dependent(false);
}
#ifdef ASSERT
@ -1893,90 +1974,26 @@ void InstanceKlass::clean_dependent_nmethods() {
#endif
}
//
// Add an nmethodBucket to the list of dependencies for this nmethod.
// It's possible that an nmethod has multiple dependencies on this klass
// so a count is kept for each bucket to guarantee that creation and
// deletion of dependencies is consistent.
//
void InstanceKlass::add_dependent_nmethod(nmethod* nm) {
assert_locked_or_safepoint(CodeCache_lock);
nmethodBucket* b = _dependencies;
nmethodBucket* last = NULL;
while (b != NULL) {
if (nm == b->get_nmethod()) {
b->increment();
return;
}
b = b->next();
}
_dependencies = new nmethodBucket(nm, _dependencies);
_dependencies = nmethodBucket::add_dependent_nmethod(_dependencies, nm);
}
//
// Decrement count of the nmethod in the dependency list and remove
// the bucket competely when the count goes to 0. This method must
// find a corresponding bucket otherwise there's a bug in the
// recording of dependecies.
//
void InstanceKlass::remove_dependent_nmethod(nmethod* nm) {
assert_locked_or_safepoint(CodeCache_lock);
nmethodBucket* b = _dependencies;
nmethodBucket* last = NULL;
while (b != NULL) {
if (nm == b->get_nmethod()) {
int val = b->decrement();
guarantee(val >= 0, err_msg("Underflow: %d", val));
if (val == 0) {
set_has_unloaded_dependent(true);
}
return;
}
last = b;
b = b->next();
}
#ifdef ASSERT
tty->print_cr("### %s can't find dependent nmethod:", this->external_name());
nm->print();
#endif // ASSERT
ShouldNotReachHere();
}
if (nmethodBucket::remove_dependent_nmethod(_dependencies, nm)) {
set_has_unloaded_dependent(true);
}
}
#ifndef PRODUCT
void InstanceKlass::print_dependent_nmethods(bool verbose) {
nmethodBucket* b = _dependencies;
int idx = 0;
while (b != NULL) {
nmethod* nm = b->get_nmethod();
tty->print("[%d] count=%d { ", idx++, b->count());
if (!verbose) {
nm->print_on(tty, "nmethod");
tty->print_cr(" } ");
} else {
nm->print();
nm->print_dependencies();
tty->print_cr("--- } ");
}
b = b->next();
}
nmethodBucket::print_dependent_nmethods(_dependencies, verbose);
}
bool InstanceKlass::is_dependent_nmethod(nmethod* nm) {
nmethodBucket* b = _dependencies;
while (b != NULL) {
if (nm == b->get_nmethod()) {
#ifdef ASSERT
int count = b->count();
assert(count >= 0, err_msg("count shouldn't be negative: %d", count));
#endif
return true;
}
b = b->next();
}
return false;
return nmethodBucket::is_dependent_nmethod(_dependencies, nm);
}
#endif //PRODUCT

View File

@ -1297,6 +1297,15 @@ class nmethodBucket: public CHeapObj<mtClass> {
nmethodBucket* next() { return _next; }
void set_next(nmethodBucket* b) { _next = b; }
nmethod* get_nmethod() { return _nmethod; }
static int mark_dependent_nmethods(nmethodBucket* deps, DepChange& changes);
static nmethodBucket* add_dependent_nmethod(nmethodBucket* deps, nmethod* nm);
static bool remove_dependent_nmethod(nmethodBucket* deps, nmethod* nm);
static nmethodBucket* clean_dependent_nmethods(nmethodBucket* deps);
#ifndef PRODUCT
static void print_dependent_nmethods(nmethodBucket* deps, bool verbose);
static bool is_dependent_nmethod(nmethodBucket* deps, nmethod* nm);
#endif //PRODUCT
};
// An iterator that's used to access the inner classes indices in the

View File

@ -942,22 +942,56 @@ int MethodHandles::find_MemberNames(KlassHandle k,
return rfill + overflow;
}
// Get context class for a CallSite instance: either extract existing context or use default one.
InstanceKlass* MethodHandles::get_call_site_context(oop call_site) {
// In order to extract a context the following traversal is performed:
// CallSite.context => Cleaner.referent => Class._klass => Klass
assert(java_lang_invoke_CallSite::is_instance(call_site), "");
oop context_oop = java_lang_invoke_CallSite::context_volatile(call_site);
if (oopDesc::is_null(context_oop)) {
return NULL; // The context hasn't been initialized yet.
void MethodHandles::add_dependent_nmethod(oop call_site, nmethod* nm) {
assert_locked_or_safepoint(CodeCache_lock);
oop context = java_lang_invoke_CallSite::context(call_site);
nmethodBucket* deps = java_lang_invoke_MethodHandleNatives_CallSiteContext::vmdependencies(context);
nmethodBucket* new_deps = nmethodBucket::add_dependent_nmethod(deps, nm);
if (deps != new_deps) {
java_lang_invoke_MethodHandleNatives_CallSiteContext::set_vmdependencies(context, new_deps);
}
oop context_class_oop = java_lang_ref_Reference::referent(context_oop);
if (oopDesc::is_null(context_class_oop)) {
// The context reference was cleared by GC, so current dependency context
// isn't usable anymore. Context should be fetched from CallSite again.
return NULL;
}
void MethodHandles::remove_dependent_nmethod(oop call_site, nmethod* nm) {
assert_locked_or_safepoint(CodeCache_lock);
oop context = java_lang_invoke_CallSite::context(call_site);
nmethodBucket* deps = java_lang_invoke_MethodHandleNatives_CallSiteContext::vmdependencies(context);
if (nmethodBucket::remove_dependent_nmethod(deps, nm)) {
nmethodBucket* new_deps = nmethodBucket::clean_dependent_nmethods(deps);
if (deps != new_deps) {
java_lang_invoke_MethodHandleNatives_CallSiteContext::set_vmdependencies(context, new_deps);
}
}
}
void MethodHandles::flush_dependent_nmethods(Handle call_site, Handle target) {
assert_lock_strong(Compile_lock);
int marked = 0;
CallSiteDepChange changes(call_site(), target());
{
MutexLockerEx mu2(CodeCache_lock, Mutex::_no_safepoint_check_flag);
oop context = java_lang_invoke_CallSite::context(call_site());
nmethodBucket* deps = java_lang_invoke_MethodHandleNatives_CallSiteContext::vmdependencies(context);
marked = nmethodBucket::mark_dependent_nmethods(deps, changes);
if (marked > 0) {
nmethodBucket* new_deps = nmethodBucket::clean_dependent_nmethods(deps);
if (deps != new_deps) {
java_lang_invoke_MethodHandleNatives_CallSiteContext::set_vmdependencies(context, new_deps);
}
}
}
if (marked > 0) {
// At least one nmethod has been marked for deoptimization
VM_Deoptimize op;
VMThread::execute(&op);
}
return InstanceKlass::cast(java_lang_Class::as_Klass(context_class_oop));
}
//------------------------------------------------------------------------------
@ -1276,7 +1310,7 @@ JVM_ENTRY(void, MHN_setCallSiteTargetNormal(JNIEnv* env, jobject igcls, jobject
{
// Walk all nmethods depending on this call site.
MutexLocker mu(Compile_lock, thread);
CodeCache::flush_dependents_on(call_site, target);
MethodHandles::flush_dependent_nmethods(call_site, target);
java_lang_invoke_CallSite::set_target(call_site(), target());
}
}
@ -1288,30 +1322,34 @@ JVM_ENTRY(void, MHN_setCallSiteTargetVolatile(JNIEnv* env, jobject igcls, jobjec
{
// Walk all nmethods depending on this call site.
MutexLocker mu(Compile_lock, thread);
CodeCache::flush_dependents_on(call_site, target);
MethodHandles::flush_dependent_nmethods(call_site, target);
java_lang_invoke_CallSite::set_target_volatile(call_site(), target());
}
}
JVM_END
JVM_ENTRY(void, MHN_invalidateDependentNMethods(JNIEnv* env, jobject igcls, jobject call_site_jh)) {
Handle call_site(THREAD, JNIHandles::resolve_non_null(call_site_jh));
JVM_ENTRY(void, MHN_clearCallSiteContext(JNIEnv* env, jobject igcls, jobject context_jh)) {
Handle context(THREAD, JNIHandles::resolve_non_null(context_jh));
{
// Walk all nmethods depending on this call site.
MutexLocker mu1(Compile_lock, thread);
CallSiteDepChange changes(call_site(), Handle());
InstanceKlass* ctxk = MethodHandles::get_call_site_context(call_site());
if (ctxk == NULL) {
return; // No dependencies to invalidate yet.
}
int marked = 0;
{
MutexLockerEx mu2(CodeCache_lock, Mutex::_no_safepoint_check_flag);
marked = ctxk->mark_dependent_nmethods(changes);
nmethodBucket* b = java_lang_invoke_MethodHandleNatives_CallSiteContext::vmdependencies(context());
while(b != NULL) {
nmethod* nm = b->get_nmethod();
if (b->count() > 0 && nm->is_alive() && !nm->is_marked_for_deoptimization()) {
nm->mark_for_deoptimization();
marked++;
}
nmethodBucket* next = b->next();
delete b;
b = next;
}
java_lang_invoke_MethodHandleNatives_CallSiteContext::set_vmdependencies(context(), NULL); // reset context
}
java_lang_invoke_CallSite::set_context_volatile(call_site(), NULL); // Reset call site to initial state
if (marked > 0) {
// At least one nmethod has been marked for deoptimization
VM_Deoptimize op;
@ -1357,6 +1395,7 @@ JVM_END
#define MT JLINV"MethodType;"
#define MH JLINV"MethodHandle;"
#define MEM JLINV"MemberName;"
#define CTX JLINV"MethodHandleNatives$CallSiteContext;"
#define CC (char*) /*cast a literal from (const char*)*/
#define FN_PTR(f) CAST_FROM_FN_PTR(void*, &f)
@ -1374,7 +1413,7 @@ static JNINativeMethod MHN_methods[] = {
{CC"objectFieldOffset", CC"("MEM")J", FN_PTR(MHN_objectFieldOffset)},
{CC"setCallSiteTargetNormal", CC"("CS""MH")V", FN_PTR(MHN_setCallSiteTargetNormal)},
{CC"setCallSiteTargetVolatile", CC"("CS""MH")V", FN_PTR(MHN_setCallSiteTargetVolatile)},
{CC"invalidateDependentNMethods", CC"("CS")V", FN_PTR(MHN_invalidateDependentNMethods)},
{CC"clearCallSiteContext", CC"("CTX")V", FN_PTR(MHN_clearCallSiteContext)},
{CC"staticFieldOffset", CC"("MEM")J", FN_PTR(MHN_staticFieldOffset)},
{CC"staticFieldBase", CC"("MEM")"OBJ, FN_PTR(MHN_staticFieldBase)},
{CC"getMemberVMInfo", CC"("MEM")"OBJ, FN_PTR(MHN_getMemberVMInfo)}

View File

@ -69,7 +69,10 @@ class MethodHandles: AllStatic {
enum { _suppress_defc = 1, _suppress_name = 2, _suppress_type = 4 };
// CallSite support
static InstanceKlass* get_call_site_context(oop call_site);
static void add_dependent_nmethod(oop call_site, nmethod* nm);
static void remove_dependent_nmethod(oop call_site, nmethod* nm);
static void flush_dependent_nmethods(Handle call_site, Handle target);
// Generate MethodHandles adapters.
static bool generate_adapters();

View File

@ -24,12 +24,15 @@
/**
* @test
* @bug 8057967
* @ignore 8079205
* @run main/bootclasspath -Xbatch java.lang.invoke.CallSiteDepContextTest
* @run main/bootclasspath -Xbatch -XX:+IgnoreUnrecognizedVMOptions -XX:+TraceClassUnloading
* -XX:+PrintCompilation -XX:+TraceDependencies -XX:+TraceReferenceGC
* -verbose:gc java.lang.invoke.CallSiteDepContextTest
*/
package java.lang.invoke;
import java.lang.ref.*;
import java.lang.reflect.Field;
import jdk.internal.org.objectweb.asm.*;
import sun.misc.Unsafe;
@ -96,6 +99,13 @@ public class CallSiteDepContextTest {
}
}
public static void testHiddenDepField() throws Exception {
try {
Field f = MethodHandleNatives.CallSiteContext.class.getDeclaredField("vmdependencies");
throw new AssertionError("Context.dependencies field should be hidden");
} catch(NoSuchFieldException e) { /* expected */ }
}
public static void testSharedCallSite() throws Throwable {
Class<?> cls1 = UNSAFE.defineAnonymousClass(Object.class, getClassFile("CS_1"), null);
Class<?> cls2 = UNSAFE.defineAnonymousClass(Object.class, getClassFile("CS_2"), null);
@ -132,12 +142,14 @@ public class CallSiteDepContextTest {
static ReferenceQueue rq = new ReferenceQueue();
static PhantomReference ref;
public static void testGC() throws Throwable {
public static void testGC(boolean clear, boolean precompile) throws Throwable {
String id = "_" + clear + "_" + precompile;
mcs = new MutableCallSite(LOOKUP.findStatic(T.class, "f1", TYPE));
Class<?>[] cls = new Class[] {
UNSAFE.defineAnonymousClass(Object.class, getClassFile("GC_1"), null),
UNSAFE.defineAnonymousClass(Object.class, getClassFile("GC_2"), null),
UNSAFE.defineAnonymousClass(Object.class, getClassFile("GC_1" + id), null),
UNSAFE.defineAnonymousClass(Object.class, getClassFile("GC_2" + id), null),
};
MethodHandle[] mhs = new MethodHandle[] {
@ -151,30 +163,38 @@ public class CallSiteDepContextTest {
execute(1, mhs);
ref = new PhantomReference<>(cls[0], rq);
cls[0] = UNSAFE.defineAnonymousClass(Object.class, getClassFile("GC_3"), null);
cls[0] = UNSAFE.defineAnonymousClass(Object.class, getClassFile("GC_3" + id), null);
mhs[0] = LOOKUP.findStatic(cls[0], METHOD_NAME, TYPE);
do {
System.gc();
try {
Reference ref1 = rq.remove(1000);
Reference ref1 = rq.remove(100);
if (ref1 == ref) {
ref1.clear();
System.gc(); // Ensure that the stale context is cleared
break;
}
} catch(InterruptedException e) { /* ignore */ }
} while (true);
execute(1, mhs);
if (clear) {
ref.clear();
System.gc(); // Ensure that the stale context is unloaded
}
if (precompile) {
execute(1, mhs);
}
mcs.setTarget(LOOKUP.findStatic(T.class, "f2", TYPE));
execute(2, mhs);
}
public static void main(String[] args) throws Throwable {
testHiddenDepField();
testSharedCallSite();
testNonBoundCallSite();
testGC();
testGC(false, false);
testGC(false, true);
testGC( true, false);
testGC( true, true);
System.out.println("TEST PASSED");
}
}