--- old/src/share/vm/classfile/javaClasses.cpp 2015-11-05 21:52:42.000000000 +0300 +++ new/src/share/vm/classfile/javaClasses.cpp 2015-11-05 21:52:42.000000000 +0300 @@ -3013,14 +3013,11 @@ } } -nmethodBucket* java_lang_invoke_MethodHandleNatives_CallSiteContext::vmdependencies(oop call_site) { +DependencyContext 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); + intptr_t* vmdeps_addr = (intptr_t*)call_site->address_field_addr(_vmdependencies_offset); + DependencyContext dep_ctx(vmdeps_addr); + return dep_ctx; } // Support for java_security_AccessControlContext --- old/src/share/vm/classfile/javaClasses.hpp 2015-11-05 21:52:43.000000000 +0300 +++ new/src/share/vm/classfile/javaClasses.hpp 2015-11-05 21:52:43.000000000 +0300 @@ -1214,11 +1214,10 @@ static int _vmdependencies_offset; static void compute_offsets(); - + public: // Accessors - static nmethodBucket* vmdependencies(oop context); - static void set_vmdependencies(oop context, nmethodBucket* bucket); + static DependencyContext vmdependencies(oop context); // Testers static bool is_subclass(Klass* klass) { --- old/src/share/vm/oops/instanceKlass.cpp 2015-11-05 21:52:43.000000000 +0300 +++ new/src/share/vm/oops/instanceKlass.cpp 2015-11-05 21:52:43.000000000 +0300 @@ -203,7 +203,6 @@ int iksize = InstanceKlass::size(vtable_len, itable_len, nonstatic_oop_map_size, access_flags.is_interface(), is_anonymous); - set_vtable_length(vtable_len); set_itable_length(itable_len); set_static_field_size(static_field_size); @@ -232,7 +231,7 @@ set_static_oop_field_count(0); set_nonstatic_field_size(0); set_is_marked_dependent(false); - set_has_unloaded_dependent(false); + _dep_context = DependencyContext::EMPTY; set_init_state(InstanceKlass::allocated); set_init_thread(NULL); set_reference_type(rt); @@ -246,7 +245,6 @@ set_annotations(NULL); set_jvmti_cached_class_field_map(NULL); set_initial_method_idnum(0); - _dependencies = NULL; set_jvmti_cached_class_field_map(NULL); set_cached_class_file(NULL); set_initial_method_idnum(0); @@ -1864,10 +1862,9 @@ // are dependent on the changes that were passed in and mark them for // deoptimization. Returns the number of nmethods found. // -int nmethodBucket::mark_dependent_nmethods(nmethodBucket* deps, DepChange& changes) { - assert_locked_or_safepoint(CodeCache_lock); +int DependencyContext::mark_dependent_nmethods(DepChange& changes) { int found = 0; - for (nmethodBucket* b = deps; b != NULL; b = b->next()) { + for (nmethodBucket* b = dependencies(); 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. @@ -1887,76 +1884,78 @@ } // -// Add an nmethodBucket to the list of dependencies for this nmethod. +// Add an nmethod to the dependency context. // 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()) { +void DependencyContext::add_dependent_nmethod(nmethod* nm, bool expunge) { + assert_lock_strong(CodeCache_lock); + for (nmethodBucket* b = dependencies(); b != NULL; b = b->next()) { if (nm == b->get_nmethod()) { b->increment(); - return deps; + return; } } - return new nmethodBucket(nm, deps); + set_dependencies(new nmethodBucket(nm, dependencies())); + + if (expunge) { + // Remove stale entries from the list. + expunge_stale_entries(); + } } // -// 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 was deleted, -// or marked ready for reclaimation. -bool nmethodBucket::remove_dependent_nmethod(nmethodBucket** deps, nmethod* nm, bool delete_immediately) { +// Decrement count of the nmethod in the dependency list and, optionally, 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. +void DependencyContext::remove_dependent_nmethod(nmethod* nm, bool expunge) { assert_locked_or_safepoint(CodeCache_lock); - - nmethodBucket* first = *deps; + nmethodBucket* first = dependencies(); nmethodBucket* last = NULL; - for (nmethodBucket* b = first; b != NULL; b = b->next()) { if (nm == b->get_nmethod()) { int val = b->decrement(); guarantee(val >= 0, "Underflow: %d", val); if (val == 0) { - if (delete_immediately) { + if (expunge) { if (last == NULL) { - *deps = b->next(); + set_dependencies(b->next()); } else { last->set_next(b->next()); } delete b; + } else { + set_has_stale_entries(true); } } - return true; + if (expunge) { + // Remove stale entries from the list. + expunge_stale_entries(); + } + return; } last = b; } - #ifdef ASSERT tty->print_raw_cr("### can't find dependent nmethod"); nm->print(); #endif // ASSERT ShouldNotReachHere(); - return false; -} - -// Convenience overload, for callers that don't want to delete the nmethodBucket entry. -bool nmethodBucket::remove_dependent_nmethod(nmethodBucket* deps, nmethod* nm) { - nmethodBucket** deps_addr = &deps; - return remove_dependent_nmethod(deps_addr, nm, false /* Don't delete */); } // -// Reclaim all unused buckets. Returns new head of the list. +// Reclaim all unused buckets. // -nmethodBucket* nmethodBucket::clean_dependent_nmethods(nmethodBucket* deps) { - nmethodBucket* first = deps; +void DependencyContext::expunge_stale_entries() { + assert_locked_or_safepoint(CodeCache_lock); + if (!has_stale_entries()) { + assert(!find_stale_entries(), "inconsistent info"); + return; + } + nmethodBucket* first = dependencies(); nmethodBucket* last = NULL; - nmethodBucket* b = first; - - while (b != NULL) { + for (nmethodBucket* b = first; b != NULL;) { assert(b->count() >= 0, "bucket count: %d", b->count()); nmethodBucket* next = b->next(); if (b->count() == 0) { @@ -1972,13 +1971,33 @@ } b = next; } - return first; + set_dependencies(first); + set_has_stale_entries(false); +} + +int DependencyContext::remove_all_dependents() { + //assert_lock_strong(CodeCache_lock); // called from InstanceKlass::release_C_heap_structures() + nmethodBucket* b = dependencies(); + set_dependencies(NULL); + int marked = 0; + 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; + } + set_has_stale_entries(false); + return marked; } #ifndef PRODUCT -void nmethodBucket::print_dependent_nmethods(nmethodBucket* deps, bool verbose) { +void DependencyContext::print_dependent_nmethods(bool verbose) { int idx = 0; - for (nmethodBucket* b = deps; b != NULL; b = b->next()) { + for (nmethodBucket* b = dependencies(); b != NULL; b = b->next()) { nmethod* nm = b->get_nmethod(); tty->print("[%d] count=%d { ", idx++, b->count()); if (!verbose) { @@ -1992,8 +2011,8 @@ } } -bool nmethodBucket::is_dependent_nmethod(nmethodBucket* deps, nmethod* nm) { - for (nmethodBucket* b = deps; b != NULL; b = b->next()) { +bool DependencyContext::is_dependent_nmethod(nmethod* nm) { + for (nmethodBucket* b = dependencies(); b != NULL; b = b->next()) { if (nm == b->get_nmethod()) { #ifdef ASSERT int count = b->count(); @@ -2004,51 +2023,40 @@ } return false; } -#endif //PRODUCT -int InstanceKlass::mark_dependent_nmethods(DepChange& changes) { - assert_locked_or_safepoint(CodeCache_lock); - return nmethodBucket::mark_dependent_nmethods(_dependencies, changes); +bool DependencyContext::find_stale_entries() { + for (nmethodBucket* b = dependencies(); b != NULL; b = b->next()) { + if (b->count() == 0) return true; + } + return false; } -void InstanceKlass::clean_dependent_nmethods() { - assert_locked_or_safepoint(CodeCache_lock); +#endif //PRODUCT - if (has_unloaded_dependent()) { - _dependencies = nmethodBucket::clean_dependent_nmethods(_dependencies); - set_has_unloaded_dependent(false); - } -#ifdef ASSERT - else { - // Verification - for (nmethodBucket* b = _dependencies; b != NULL; b = b->next()) { - assert(b->count() >= 0, "bucket count: %d", b->count()); - assert(b->count() != 0, "empty buckets need to be cleaned"); - } - } -#endif +int InstanceKlass::mark_dependent_nmethods(DepChange& changes) { + DependencyContext dep_context(&_dep_context); + return dep_context.mark_dependent_nmethods(changes); } void InstanceKlass::add_dependent_nmethod(nmethod* nm) { - assert_locked_or_safepoint(CodeCache_lock); - _dependencies = nmethodBucket::add_dependent_nmethod(_dependencies, nm); + DependencyContext dep_context(&_dep_context); + dep_context.add_dependent_nmethod(nm); } void InstanceKlass::remove_dependent_nmethod(nmethod* nm, bool delete_immediately) { - assert_locked_or_safepoint(CodeCache_lock); - - if (nmethodBucket::remove_dependent_nmethod(&_dependencies, nm, delete_immediately)) { - set_has_unloaded_dependent(true); - } + DependencyContext dep_context(&_dep_context); + dep_context.remove_dependent_nmethod(nm, delete_immediately); } #ifndef PRODUCT void InstanceKlass::print_dependent_nmethods(bool verbose) { - nmethodBucket::print_dependent_nmethods(_dependencies, verbose); + DependencyContext dep_context(&_dep_context); + dep_context.print_dependent_nmethods(verbose); } bool InstanceKlass::is_dependent_nmethod(nmethod* nm) { - return nmethodBucket::is_dependent_nmethod(_dependencies, nm); + DependencyContext dep_context(&_dep_context); + return dep_context.is_dependent_nmethod(nm); } #endif //PRODUCT @@ -2056,7 +2064,9 @@ clean_implementors_list(is_alive); clean_method_data(is_alive); - clean_dependent_nmethods(); + // Since GC iterates InstanceKlasses sequentially, it is safe to remove stale entries here. + DependencyContext dep_context(&_dep_context); + dep_context.expunge_stale_entries(); } void InstanceKlass::clean_implementors_list(BoolObjectClosure* is_alive) { @@ -2103,6 +2113,8 @@ constants()->remove_unshareable_info(); + assert(_dep_context == DependencyContext::EMPTY, "dependency context is not shareable"); + for (int i = 0; i < methods()->length(); i++) { Method* m = methods()->at(i); m->remove_unshareable_info(); @@ -2232,12 +2244,10 @@ } // release dependencies - nmethodBucket* b = _dependencies; - _dependencies = NULL; - while (b != NULL) { - nmethodBucket* next = b->next(); - delete b; - b = next; + { + DependencyContext ctx(&_dep_context); + int marked = ctx.remove_all_dependents(); + assert(marked == 0, "all dependencies should be already invalidated"); } // Deallocate breakpoint records @@ -3580,193 +3590,83 @@ #ifndef PRODUCT -class TestNmethodBucketContext { +class TestDependencyContext { public: - nmethod* _nmethodLast; - nmethod* _nmethodMiddle; - nmethod* _nmethodFirst; - - nmethodBucket* _bucketLast; - nmethodBucket* _bucketMiddle; - nmethodBucket* _bucketFirst; + nmethod* _nmethods[3]; - nmethodBucket* _bucketList; + intptr_t _dependency_context; - TestNmethodBucketContext() { + TestDependencyContext() : _dependency_context(DependencyContext::EMPTY) { CodeCache_lock->lock_without_safepoint_check(); - _nmethodLast = reinterpret_cast(0x8 * 0); - _nmethodMiddle = reinterpret_cast(0x8 * 1); - _nmethodFirst = reinterpret_cast(0x8 * 2); + DependencyContext depContext(&_dependency_context); - _bucketLast = new nmethodBucket(_nmethodLast, NULL); - _bucketMiddle = new nmethodBucket(_nmethodMiddle, _bucketLast); - _bucketFirst = new nmethodBucket(_nmethodFirst, _bucketMiddle); + _nmethods[0] = reinterpret_cast(0x8 * 0); + _nmethods[1] = reinterpret_cast(0x8 * 1); + _nmethods[2] = reinterpret_cast(0x8 * 2); - _bucketList = _bucketFirst; + depContext.add_dependent_nmethod(_nmethods[2]); + depContext.add_dependent_nmethod(_nmethods[1]); + depContext.add_dependent_nmethod(_nmethods[0]); } - ~TestNmethodBucketContext() { - delete _bucketLast; - delete _bucketMiddle; - delete _bucketFirst; - + ~TestDependencyContext() { + wipe(); CodeCache_lock->unlock(); } -}; - -class TestNmethodBucket { - public: - static void testRemoveDependentNmethodFirstDeleteImmediately() { - TestNmethodBucketContext c; - - nmethodBucket::remove_dependent_nmethod(&c._bucketList, c._nmethodFirst, true /* delete */); - - assert(c._bucketList == c._bucketMiddle, "check"); - assert(c._bucketList->next() == c._bucketLast, "check"); - assert(c._bucketList->next()->next() == NULL, "check"); - - // Cleanup before context is deleted. - c._bucketFirst = NULL; - } - - static void testRemoveDependentNmethodMiddleDeleteImmediately() { - TestNmethodBucketContext c; - - nmethodBucket::remove_dependent_nmethod(&c._bucketList, c._nmethodMiddle, true /* delete */); - - assert(c._bucketList == c._bucketFirst, "check"); - assert(c._bucketList->next() == c._bucketLast, "check"); - assert(c._bucketList->next()->next() == NULL, "check"); - - // Cleanup before context is deleted. - c._bucketMiddle = NULL; - } - - static void testRemoveDependentNmethodLastDeleteImmediately() { - TestNmethodBucketContext c; - - nmethodBucket::remove_dependent_nmethod(&c._bucketList, c._nmethodLast, true /* delete */); - - assert(c._bucketList == c._bucketFirst, "check"); - assert(c._bucketList->next() == c._bucketMiddle, "check"); - assert(c._bucketList->next()->next() == NULL, "check"); - - // Cleanup before context is deleted. - c._bucketLast = NULL; - } - - static void testRemoveDependentNmethodFirstDeleteDeferred() { - TestNmethodBucketContext c; - - nmethodBucket::remove_dependent_nmethod(&c._bucketList, c._nmethodFirst, false /* delete */); - - assert(c._bucketList == c._bucketFirst, "check"); - assert(c._bucketList->next() == c._bucketMiddle, "check"); - assert(c._bucketList->next()->next() == c._bucketLast, "check"); - assert(c._bucketList->next()->next()->next() == NULL, "check"); - - assert(c._bucketFirst->count() == 0, "check"); - assert(c._bucketMiddle->count() == 1, "check"); - assert(c._bucketLast->count() == 1, "check"); - } - - static void testRemoveDependentNmethodMiddleDeleteDeferred() { - TestNmethodBucketContext c; - - nmethodBucket::remove_dependent_nmethod(&c._bucketList, c._nmethodMiddle, false /* delete */); - - assert(c._bucketList == c._bucketFirst, "check"); - assert(c._bucketList->next() == c._bucketMiddle, "check"); - assert(c._bucketList->next()->next() == c._bucketLast, "check"); - assert(c._bucketList->next()->next()->next() == NULL, "check"); - - assert(c._bucketFirst->count() == 1, "check"); - assert(c._bucketMiddle->count() == 0, "check"); - assert(c._bucketLast->count() == 1, "check"); - } - - static void testRemoveDependentNmethodLastDeleteDeferred() { - TestNmethodBucketContext c; - - nmethodBucket::remove_dependent_nmethod(&c._bucketList, c._nmethodLast, false /* delete */); - - assert(c._bucketList == c._bucketFirst, "check"); - assert(c._bucketList->next() == c._bucketMiddle, "check"); - assert(c._bucketList->next()->next() == c._bucketLast, "check"); - assert(c._bucketList->next()->next()->next() == NULL, "check"); - assert(c._bucketFirst->count() == 1, "check"); - assert(c._bucketMiddle->count() == 1, "check"); - assert(c._bucketLast->count() == 0, "check"); - } - - static void testRemoveDependentNmethodConvenienceFirst() { - TestNmethodBucketContext c; - - nmethodBucket::remove_dependent_nmethod(c._bucketList, c._nmethodFirst); - - assert(c._bucketList == c._bucketFirst, "check"); - assert(c._bucketList->next() == c._bucketMiddle, "check"); - assert(c._bucketList->next()->next() == c._bucketLast, "check"); - assert(c._bucketList->next()->next()->next() == NULL, "check"); + static void testRemoveDependentNmethod(int id, bool delete_immediately) { + TestDependencyContext c; + DependencyContext depContext(&c._dependency_context); + assert(!has_stale_entries(depContext), "check"); + + nmethod* nm = c._nmethods[id]; + depContext.remove_dependent_nmethod(nm, delete_immediately); + + if (!delete_immediately) { + assert(has_stale_entries(depContext), "check"); + assert(depContext.is_dependent_nmethod(nm), "check"); + depContext.expunge_stale_entries(); + } - assert(c._bucketFirst->count() == 0, "check"); - assert(c._bucketMiddle->count() == 1, "check"); - assert(c._bucketLast->count() == 1, "check"); + assert(!has_stale_entries(depContext), "check"); + assert(!depContext.is_dependent_nmethod(nm), "check"); } - static void testRemoveDependentNmethodConvenienceMiddle() { - TestNmethodBucketContext c; - - nmethodBucket::remove_dependent_nmethod(c._bucketList, c._nmethodMiddle); - - assert(c._bucketList == c._bucketFirst, "check"); - assert(c._bucketList->next() == c._bucketMiddle, "check"); - assert(c._bucketList->next()->next() == c._bucketLast, "check"); - assert(c._bucketList->next()->next()->next() == NULL, "check"); - - assert(c._bucketFirst->count() == 1, "check"); - assert(c._bucketMiddle->count() == 0, "check"); - assert(c._bucketLast->count() == 1, "check"); + static void testRemoveDependentNmethod() { + testRemoveDependentNmethod(0, false); + testRemoveDependentNmethod(1, false); + testRemoveDependentNmethod(2, false); + + testRemoveDependentNmethod(0, true); + testRemoveDependentNmethod(1, true); + testRemoveDependentNmethod(2, true); } - static void testRemoveDependentNmethodConvenienceLast() { - TestNmethodBucketContext c; - - nmethodBucket::remove_dependent_nmethod(c._bucketList, c._nmethodLast); - - assert(c._bucketList == c._bucketFirst, "check"); - assert(c._bucketList->next() == c._bucketMiddle, "check"); - assert(c._bucketList->next()->next() == c._bucketLast, "check"); - assert(c._bucketList->next()->next()->next() == NULL, "check"); - - assert(c._bucketFirst->count() == 1, "check"); - assert(c._bucketMiddle->count() == 1, "check"); - assert(c._bucketLast->count() == 0, "check"); + static void test() { + testRemoveDependentNmethod(); } - static void testRemoveDependentNmethod() { - testRemoveDependentNmethodFirstDeleteImmediately(); - testRemoveDependentNmethodMiddleDeleteImmediately(); - testRemoveDependentNmethodLastDeleteImmediately(); - - testRemoveDependentNmethodFirstDeleteDeferred(); - testRemoveDependentNmethodMiddleDeleteDeferred(); - testRemoveDependentNmethodLastDeleteDeferred(); - - testRemoveDependentNmethodConvenienceFirst(); - testRemoveDependentNmethodConvenienceMiddle(); - testRemoveDependentNmethodConvenienceLast(); + static bool has_stale_entries(DependencyContext ctx) { + assert(ctx.has_stale_entries() == ctx.find_stale_entries(), "check"); + return ctx.has_stale_entries(); } - static void test() { - testRemoveDependentNmethod(); + void wipe() { + DependencyContext ctx(&_dependency_context); + nmethodBucket* b = ctx.dependencies(); + ctx.set_dependencies(NULL); + ctx.set_has_stale_entries(false); + while (b != NULL) { + nmethodBucket* next = b->next(); + delete b; + b = next; + } } }; -void TestNmethodBucket_test() { - TestNmethodBucket::test(); +void TestDependencyContext_test() { + TestDependencyContext::test(); } #endif --- old/src/share/vm/oops/instanceKlass.hpp 2015-11-05 21:52:44.000000000 +0300 +++ new/src/share/vm/oops/instanceKlass.hpp 2015-11-05 21:52:44.000000000 +0300 @@ -104,6 +104,68 @@ uint _count; }; +// Utility class to manipulate nmethod dependency context. +// The context consists of nmethodBucket* (a head of a linked list) +// and a boolean flag (does the list contains stale entries). The structure is +// encoded as an intptr_t: lower bit is used for the flag. It is possible since +// nmethodBucket* is aligned - the structure is malloc'ed in C heap. +// Dependency context can be attached either to an InstanceKlass (_dep_context field) +// or CallSiteContext oop for call_site_target dependencies (see javaClasses.hpp). +// DependencyContext class operates on some location which holds a intptr_t value. +class DependencyContext : public StackObj { + friend class VMStructs; + friend class TestDependencyContext; + private: + enum TagBits { _has_stale_entries_bit = 1, _has_stale_entries_mask = 1 }; + + intptr_t* _dependency_context_addr; + + void set_dependencies(nmethodBucket* b) { + assert((intptr_t(b) & _has_stale_entries_mask) == 0, "should be aligned"); + if (has_stale_entries()) { + *_dependency_context_addr = intptr_t(b) | _has_stale_entries_mask; + } else { + *_dependency_context_addr = intptr_t(b); + } + } + + void set_has_stale_entries(bool b) { + if (b) { + *_dependency_context_addr |= _has_stale_entries_mask; + } else { + *_dependency_context_addr &= ~_has_stale_entries_mask; + } + } + + nmethodBucket* dependencies() { + intptr_t value = *_dependency_context_addr; + return (nmethodBucket*) (value & ~_has_stale_entries_mask); + } + + bool has_stale_entries() const { + intptr_t value = *_dependency_context_addr; + return (value & _has_stale_entries_mask) != 0; + }; + + public: + DependencyContext(intptr_t* addr) : _dependency_context_addr(addr) {} + + static const intptr_t EMPTY = 0; // dependencies = NULL, has_stale_entries = false + + int mark_dependent_nmethods(DepChange& changes); + void add_dependent_nmethod(nmethod* nm, bool expunge_stale_entries = false); + void remove_dependent_nmethod(nmethod* nm, bool expunge_stale_entries = false); + int remove_all_dependents(); + + void expunge_stale_entries(); + +#ifndef PRODUCT + void print_dependent_nmethods(bool verbose); + bool is_dependent_nmethod(nmethod* nm); + bool find_stale_entries(); +#endif //PRODUCT +}; + struct JvmtiCachedClassFileData; class InstanceKlass: public Klass { @@ -198,7 +260,6 @@ // _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 - bool _has_unloaded_dependent; // The low two bits of _misc_flags contains the kind field. // This can be used to quickly discriminate among the four kinds of @@ -235,7 +296,7 @@ MemberNameTable* _member_names; // Member names JNIid* _jni_ids; // First JNI identifier for static fields in this class jmethodID* _methods_jmethod_ids; // jmethodIDs corresponding to method_idnum, or NULL if none - nmethodBucket* _dependencies; // list of dependent nmethods + intptr_t _dep_context; // packed DependencyContext structure nmethod* _osr_nmethods_head; // Head of list of on-stack replacement nmethods for this class BreakpointInfo* _breakpoints; // bpt lists, managed by Method* // Linked instanceKlasses of previous versions @@ -468,9 +529,6 @@ bool is_marked_dependent() const { return _is_marked_dependent; } void set_is_marked_dependent(bool value) { _is_marked_dependent = value; } - bool has_unloaded_dependent() const { return _has_unloaded_dependent; } - void set_has_unloaded_dependent(bool value) { _has_unloaded_dependent = value; } - // initialization (virtuals from Klass) bool should_be_initialized() const; // means that initialize should be called void initialize(TRAPS); @@ -835,7 +893,7 @@ JNIid* jni_id_for(int offset); // maintenance of deoptimization dependencies - int mark_dependent_nmethods(DepChange& changes); + int mark_dependent_nmethods(DepChange& changes); void add_dependent_nmethod(nmethod* nm); void remove_dependent_nmethod(nmethod* nm, bool delete_immediately); @@ -1024,7 +1082,6 @@ void clean_weak_instanceklass_links(BoolObjectClosure* is_alive); void clean_implementors_list(BoolObjectClosure* is_alive); void clean_method_data(BoolObjectClosure* is_alive); - void clean_dependent_nmethods(); // Explicit metaspace deallocation of fields // For RedefineClasses and class file parsing errors, we need to deallocate @@ -1336,27 +1393,15 @@ nmethodBucket* _next; public: - nmethodBucket(nmethod* nmethod, nmethodBucket* next) { - _nmethod = nmethod; - _next = next; - _count = 1; - } + nmethodBucket(nmethod* nmethod, nmethodBucket* next) : + _nmethod(nmethod), _next(next), _count(1) {} + int count() { return _count; } int increment() { _count += 1; return _count; } int decrement(); 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, bool delete_immediately); - 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 --- old/src/share/vm/prims/jni.cpp 2015-11-05 21:52:44.000000000 +0300 +++ new/src/share/vm/prims/jni.cpp 2015-11-05 21:52:44.000000000 +0300 @@ -3854,7 +3854,7 @@ unit_test_function_call // Forward declaration -void TestNmethodBucket_test(); +void TestDependencyContext_test(); void test_semaphore(); void TestOS_test(); void TestReservedSpace_test(); @@ -3883,7 +3883,7 @@ void execute_internal_vm_tests() { if (ExecuteInternalVMTests) { tty->print_cr("Running internal VM tests"); - run_unit_test(TestNmethodBucket_test()); + run_unit_test(TestDependencyContext_test()); run_unit_test(test_semaphore()); run_unit_test(TestOS_test()); run_unit_test(TestReservedSpace_test()); --- old/src/share/vm/prims/methodHandles.cpp 2015-11-05 21:52:45.000000000 +0300 +++ new/src/share/vm/prims/methodHandles.cpp 2015-11-05 21:52:45.000000000 +0300 @@ -945,30 +945,33 @@ return rfill + overflow; } +// Is it safe to remove stale entries from a dependency list? +static bool safe_to_expunge() { + // Since parallel GC threads can concurrently iterate over a dependency + // list during safepoint, it is safe to remove entries only when + // CodeCache lock is held. + return CodeCache_lock->owned_by_self(); +} + 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); - } + DependencyContext deps = java_lang_invoke_MethodHandleNatives_CallSiteContext::vmdependencies(context); + // Try to purge stale entries on updates. + // Since GC doesn't clean dependency contexts rooted at CallSiteContext objects, + // in order to avoid memory leak, stale entries are purged whenever a dependency list + // is changed (both on addition and removal). Though memory reclamation is delayed, + // it avoids indefinite memory usage growth. + deps.add_dependent_nmethod(nm, /*expunge_stale_entries=*/safe_to_expunge()); } 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); - } - } + DependencyContext deps = java_lang_invoke_MethodHandleNatives_CallSiteContext::vmdependencies(context); + deps.remove_dependent_nmethod(nm, /*expunge_stale_entries=*/safe_to_expunge()); } void MethodHandles::flush_dependent_nmethods(Handle call_site, Handle target) { @@ -977,21 +980,15 @@ int marked = 0; CallSiteDepChange changes(call_site(), target()); { + No_Safepoint_Verifier nsv; 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); - } - } + DependencyContext deps = java_lang_invoke_MethodHandleNatives_CallSiteContext::vmdependencies(context); + marked = deps.mark_dependent_nmethods(changes); } if (marked > 0) { - // At least one nmethod has been marked for deoptimization + // At least one nmethod has been marked for deoptimization. VM_Deoptimize op; VMThread::execute(&op); } @@ -1339,19 +1336,10 @@ int marked = 0; { + No_Safepoint_Verifier nsv; MutexLockerEx mu2(CodeCache_lock, Mutex::_no_safepoint_check_flag); - 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 + DependencyContext deps = java_lang_invoke_MethodHandleNatives_CallSiteContext::vmdependencies(context()); + marked = deps.remove_all_dependents(); } if (marked > 0) { // At least one nmethod has been marked for deoptimization --- old/src/share/vm/runtime/vmStructs.cpp 2015-11-05 21:52:45.000000000 +0300 +++ new/src/share/vm/runtime/vmStructs.cpp 2015-11-05 21:52:45.000000000 +0300 @@ -342,7 +342,8 @@ nonstatic_field(InstanceKlass, _methods_jmethod_ids, jmethodID*) \ volatile_nonstatic_field(InstanceKlass, _idnum_allocated_count, u2) \ nonstatic_field(InstanceKlass, _annotations, Annotations*) \ - nonstatic_field(InstanceKlass, _dependencies, nmethodBucket*) \ + nonstatic_field(InstanceKlass, _dep_context, intptr_t) \ + nonstatic_field(DependencyContext, _dependency_context_addr, intptr_t*) \ nonstatic_field(nmethodBucket, _nmethod, nmethod*) \ nonstatic_field(nmethodBucket, _count, int) \ nonstatic_field(nmethodBucket, _next, nmethodBucket*) \ @@ -1553,6 +1554,7 @@ declare_toplevel_type(volatile Metadata*) \ \ declare_toplevel_type(DataLayout) \ + declare_toplevel_type(DependencyContext) \ declare_toplevel_type(nmethodBucket) \ \ /********/ \