8209189: Make CompiledMethod::do_unloading more concurrent
Reviewed-by: kvn, coleenp
--- a/src/hotspot/share/aot/aotCodeHeap.cpp Thu Nov 01 14:57:26 2018 +0100
+++ b/src/hotspot/share/aot/aotCodeHeap.cpp Fri Nov 02 08:33:59 2018 +0100
@@ -926,7 +926,7 @@
continue; // Skip uninitialized entries.
}
AOTCompiledMethod* aot = _code_to_aot[index]._aot;
- aot->cleanup_inline_caches();
+ aot->cleanup_inline_caches(false);
}
}
--- a/src/hotspot/share/aot/aotCompiledMethod.cpp Thu Nov 01 14:57:26 2018 +0100
+++ b/src/hotspot/share/aot/aotCompiledMethod.cpp Fri Nov 02 08:33:59 2018 +0100
@@ -75,10 +75,6 @@
return (address*) ((address)fr->unextended_sp() + _meta->orig_pc_offset());
}
-bool AOTCompiledMethod::do_unloading_oops(address low_boundary, BoolObjectClosure* is_alive) {
- return false;
-}
-
oop AOTCompiledMethod::oop_at(int index) const {
if (index == 0) { // 0 is reserved
return NULL;
@@ -352,7 +348,7 @@
log->print(" aot='%2d'", _heap->dso_id());
}
-void AOTCompiledMethod::log_state_change(oop cause) const {
+void AOTCompiledMethod::log_state_change() const {
if (LogCompilation) {
ResourceMark m;
if (xtty != NULL) {
--- a/src/hotspot/share/aot/aotCompiledMethod.hpp Thu Nov 01 14:57:26 2018 +0100
+++ b/src/hotspot/share/aot/aotCompiledMethod.hpp Fri Nov 02 08:33:59 2018 +0100
@@ -193,7 +193,7 @@
virtual int comp_level() const { return CompLevel_aot; }
virtual address verified_entry_point() const { return _code + _meta->verified_entry_offset(); }
virtual void log_identity(xmlStream* stream) const;
- virtual void log_state_change(oop cause = NULL) const;
+ virtual void log_state_change() const;
virtual bool make_entrant() NOT_TIERED({ ShouldNotReachHere(); return false; });
virtual bool make_not_entrant() { return make_not_entrant_helper(not_entrant); }
virtual bool make_not_used() { return make_not_entrant_helper(not_used); }
@@ -277,11 +277,6 @@
CompiledStaticCall* compiledStaticCall_before(address addr) const;
private:
bool is_aot_runtime_stub() const { return _method == NULL; }
-
-protected:
- virtual bool do_unloading_oops(address low_boundary, BoolObjectClosure* is_alive);
- virtual bool do_unloading_jvmci() { return false; }
-
};
class PltNativeCallWrapper: public NativeCallWrapper {
--- a/src/hotspot/share/code/codeCache.cpp Thu Nov 01 14:57:26 2018 +0100
+++ b/src/hotspot/share/code/codeCache.cpp Fri Nov 02 08:33:59 2018 +0100
@@ -144,7 +144,6 @@
address CodeCache::_low_bound = 0;
address CodeCache::_high_bound = 0;
int CodeCache::_number_of_nmethods_with_dependencies = 0;
-bool CodeCache::_needs_cache_clean = false;
nmethod* CodeCache::_scavenge_root_nmethods = NULL;
// Initialize arrays of CodeHeap subsets
@@ -683,17 +682,11 @@
// Mark nmethods for unloading if they contain otherwise unreachable oops.
void CodeCache::do_unloading(BoolObjectClosure* is_alive, bool unloading_occurred) {
assert_locked_or_safepoint(CodeCache_lock);
+ UnloadingScope scope(is_alive);
CompiledMethodIterator iter;
while(iter.next_alive()) {
- iter.method()->do_unloading(is_alive);
+ iter.method()->do_unloading(unloading_occurred);
}
-
- // Now that all the unloaded nmethods are known, cleanup caches
- // before CLDG is purged.
- // This is another code cache walk but it is moved from gc_epilogue.
- // G1 does a parallel walk of the nmethods so cleans them up
- // as it goes and doesn't call this.
- do_unloading_nmethod_caches(unloading_occurred);
}
void CodeCache::blobs_do(CodeBlobClosure* f) {
@@ -908,28 +901,14 @@
prune_scavenge_root_nmethods();
}
+uint8_t CodeCache::_unloading_cycle = 1;
-void CodeCache::do_unloading_nmethod_caches(bool class_unloading_occurred) {
- assert_locked_or_safepoint(CodeCache_lock);
- // Even if classes are not unloaded, there may have been some nmethods that are
- // unloaded because oops in them are no longer reachable.
- NOT_DEBUG(if (needs_cache_clean() || class_unloading_occurred)) {
- CompiledMethodIterator iter;
- while(iter.next_alive()) {
- CompiledMethod* cm = iter.method();
- assert(!cm->is_unloaded(), "Tautology");
- DEBUG_ONLY(if (needs_cache_clean() || class_unloading_occurred)) {
- // Clean up both unloaded klasses from nmethods and unloaded nmethods
- // from inline caches.
- cm->unload_nmethod_caches(/*parallel*/false, class_unloading_occurred);
- }
- DEBUG_ONLY(cm->verify());
- DEBUG_ONLY(cm->verify_oop_relocations());
- }
+void CodeCache::increment_unloading_cycle() {
+ if (_unloading_cycle == 1) {
+ _unloading_cycle = 2;
+ } else {
+ _unloading_cycle = 1;
}
-
- set_needs_cache_clean(false);
- verify_icholder_relocations();
}
void CodeCache::verify_oops() {
--- a/src/hotspot/share/code/codeCache.hpp Thu Nov 01 14:57:26 2018 +0100
+++ b/src/hotspot/share/code/codeCache.hpp Fri Nov 02 08:33:59 2018 +0100
@@ -27,6 +27,7 @@
#include "code/codeBlob.hpp"
#include "code/nmethod.hpp"
+#include "gc/shared/gcBehaviours.hpp"
#include "memory/allocation.hpp"
#include "memory/heap.hpp"
#include "oops/instanceKlass.hpp"
@@ -90,8 +91,8 @@
static address _low_bound; // Lower bound of CodeHeap addresses
static address _high_bound; // Upper bound of CodeHeap addresses
static int _number_of_nmethods_with_dependencies; // Total number of nmethods with dependencies
- static bool _needs_cache_clean; // True if inline caches of the nmethods needs to be flushed
static nmethod* _scavenge_root_nmethods; // linked via nm->scavenge_root_link()
+ static uint8_t _unloading_cycle; // Global state for recognizing old nmethods that need to be unloaded
static void mark_scavenge_root_nmethods() PRODUCT_RETURN;
static void verify_perm_nmethods(CodeBlobClosure* f_or_null) PRODUCT_RETURN;
@@ -172,7 +173,24 @@
// to) any unmarked codeBlobs in the cache. Sets "marked_for_unloading"
// to "true" iff some code got unloaded.
// "unloading_occurred" controls whether metadata should be cleaned because of class unloading.
+ class UnloadingScope: StackObj {
+ ClosureIsUnloadingBehaviour _is_unloading_behaviour;
+
+ public:
+ UnloadingScope(BoolObjectClosure* is_alive)
+ : _is_unloading_behaviour(is_alive)
+ {
+ IsUnloadingBehaviour::set_current(&_is_unloading_behaviour);
+ increment_unloading_cycle();
+ }
+
+ ~UnloadingScope() {
+ IsUnloadingBehaviour::set_current(NULL);
+ }
+ };
static void do_unloading(BoolObjectClosure* is_alive, bool unloading_occurred);
+ static uint16_t unloading_cycle() { return _unloading_cycle; }
+ static void increment_unloading_cycle();
static void asserted_non_scavengable_nmethods_do(CodeBlobClosure* f = NULL) PRODUCT_RETURN;
// Apply f to every live code blob in scavengable nmethods. Prune nmethods
@@ -222,12 +240,8 @@
static double reverse_free_ratio(int code_blob_type);
- static bool needs_cache_clean() { return _needs_cache_clean; }
- static void set_needs_cache_clean(bool v) { _needs_cache_clean = v; }
-
static void clear_inline_caches(); // clear all inline caches
static void cleanup_inline_caches(); // clean unloaded/zombie nmethods from inline caches
- static void do_unloading_nmethod_caches(bool class_unloading_occurred); // clean all nmethod caches for unloading, including inline caches
// Returns true if an own CodeHeap for the given CodeBlobType is available
static bool heap_available(int code_blob_type);
--- a/src/hotspot/share/code/compiledMethod.cpp Thu Nov 01 14:57:26 2018 +0100
+++ b/src/hotspot/share/code/compiledMethod.cpp Fri Nov 02 08:33:59 2018 +0100
@@ -27,6 +27,8 @@
#include "code/compiledMethod.inline.hpp"
#include "code/scopeDesc.hpp"
#include "code/codeCache.hpp"
+#include "gc/shared/barrierSet.hpp"
+#include "gc/shared/gcBehaviours.hpp"
#include "interpreter/bytecode.inline.hpp"
#include "logging/log.hpp"
#include "logging/logTag.hpp"
@@ -37,16 +39,29 @@
#include "runtime/handles.inline.hpp"
#include "runtime/mutexLocker.hpp"
-CompiledMethod::CompiledMethod(Method* method, const char* name, CompilerType type, const CodeBlobLayout& layout, int frame_complete_offset, int frame_size, ImmutableOopMapSet* oop_maps, bool caller_must_gc_arguments)
+CompiledMethod::CompiledMethod(Method* method, const char* name, CompilerType type, const CodeBlobLayout& layout,
+ int frame_complete_offset, int frame_size, ImmutableOopMapSet* oop_maps,
+ bool caller_must_gc_arguments)
: CodeBlob(name, type, layout, frame_complete_offset, frame_size, oop_maps, caller_must_gc_arguments),
- _mark_for_deoptimization_status(not_marked), _method(method) {
+ _mark_for_deoptimization_status(not_marked),
+ _is_unloading_state(0),
+ _method(method)
+{
init_defaults();
+ clear_unloading_state();
}
-CompiledMethod::CompiledMethod(Method* method, const char* name, CompilerType type, int size, int header_size, CodeBuffer* cb, int frame_complete_offset, int frame_size, OopMapSet* oop_maps, bool caller_must_gc_arguments)
- : CodeBlob(name, type, CodeBlobLayout((address) this, size, header_size, cb), cb, frame_complete_offset, frame_size, oop_maps, caller_must_gc_arguments),
- _mark_for_deoptimization_status(not_marked), _method(method) {
+CompiledMethod::CompiledMethod(Method* method, const char* name, CompilerType type, int size,
+ int header_size, CodeBuffer* cb, int frame_complete_offset, int frame_size,
+ OopMapSet* oop_maps, bool caller_must_gc_arguments)
+ : CodeBlob(name, type, CodeBlobLayout((address) this, size, header_size, cb), cb,
+ frame_complete_offset, frame_size, oop_maps, caller_must_gc_arguments),
+ _mark_for_deoptimization_status(not_marked),
+ _is_unloading_state(0),
+ _method(method)
+{
init_defaults();
+ clear_unloading_state();
}
void CompiledMethod::init_defaults() {
@@ -54,7 +69,6 @@
_has_method_handle_invokes = 0;
_lazy_critical_native = 0;
_has_wide_vectors = 0;
- _unloading_clock = 0;
}
bool CompiledMethod::is_method_handle_return(address return_pc) {
@@ -385,26 +399,6 @@
ic->set_to_clean();
}
-unsigned char CompiledMethod::_global_unloading_clock = 0;
-
-void CompiledMethod::increase_unloading_clock() {
- _global_unloading_clock++;
- if (_global_unloading_clock == 0) {
- // _nmethods are allocated with _unloading_clock == 0,
- // so 0 is never used as a clock value.
- _global_unloading_clock = 1;
- }
-}
-
-void CompiledMethod::set_unloading_clock(unsigned char unloading_clock) {
- OrderAccess::release_store(&_unloading_clock, unloading_clock);
-}
-
-unsigned char CompiledMethod::unloading_clock() {
- return OrderAccess::load_acquire(&_unloading_clock);
-}
-
-
// static_stub_Relocations may have dangling references to
// nmethods so trim them out here. Otherwise it looks like
// compiled code is maintaining a link to dead metadata.
@@ -438,84 +432,30 @@
#endif
}
-// This is called at the end of the strong tracing/marking phase of a
-// GC to unload an nmethod if it contains otherwise unreachable
-// oops.
-
-void CompiledMethod::do_unloading(BoolObjectClosure* is_alive) {
- // Make sure the oop's ready to receive visitors
- assert(!is_zombie() && !is_unloaded(),
- "should not call follow on zombie or unloaded nmethod");
-
- address low_boundary = oops_reloc_begin();
-
- if (do_unloading_oops(low_boundary, is_alive)) {
- return;
- }
-
-#if INCLUDE_JVMCI
- if (do_unloading_jvmci()) {
- return;
- }
-#endif
-
- // Cleanup exception cache and inline caches happens
- // after all the unloaded methods are found.
-}
-
// Clean references to unloaded nmethods at addr from this one, which is not unloaded.
template <class CompiledICorStaticCall>
-static bool clean_if_nmethod_is_unloaded(CompiledICorStaticCall *ic, address addr, CompiledMethod* from,
- bool parallel, bool clean_all) {
+static void clean_if_nmethod_is_unloaded(CompiledICorStaticCall *ic, address addr, CompiledMethod* from,
+ bool clean_all) {
// Ok, to lookup references to zombies here
CodeBlob *cb = CodeCache::find_blob_unsafe(addr);
CompiledMethod* nm = (cb != NULL) ? cb->as_compiled_method_or_null() : NULL;
if (nm != NULL) {
- if (parallel && nm->unloading_clock() != CompiledMethod::global_unloading_clock()) {
- // The nmethod has not been processed yet.
- return true;
- }
-
// Clean inline caches pointing to both zombie and not_entrant methods
- if (clean_all || !nm->is_in_use() || (nm->method()->code() != nm)) {
+ if (clean_all || !nm->is_in_use() || nm->is_unloading() || (nm->method()->code() != nm)) {
ic->set_to_clean(from->is_alive());
assert(ic->is_clean(), "nmethod " PTR_FORMAT "not clean %s", p2i(from), from->method()->name_and_sig_as_C_string());
}
}
-
- return false;
-}
-
-static bool clean_if_nmethod_is_unloaded(CompiledIC *ic, CompiledMethod* from,
- bool parallel, bool clean_all = false) {
- return clean_if_nmethod_is_unloaded(ic, ic->ic_destination(), from, parallel, clean_all);
-}
-
-static bool clean_if_nmethod_is_unloaded(CompiledStaticCall *csc, CompiledMethod* from,
- bool parallel, bool clean_all = false) {
- return clean_if_nmethod_is_unloaded(csc, csc->destination(), from, parallel, clean_all);
}
-bool CompiledMethod::do_unloading_parallel(BoolObjectClosure* is_alive, bool unloading_occurred) {
- ResourceMark rm;
-
- // Make sure the oop's ready to receive visitors
- assert(!is_zombie() && !is_unloaded(),
- "should not call follow on zombie or unloaded nmethod");
-
- address low_boundary = oops_reloc_begin();
+static void clean_if_nmethod_is_unloaded(CompiledIC *ic, CompiledMethod* from,
+ bool clean_all) {
+ clean_if_nmethod_is_unloaded(ic, ic->ic_destination(), from, clean_all);
+}
- if (do_unloading_oops(low_boundary, is_alive)) {
- return false;
- }
-
-#if INCLUDE_JVMCI
- if (do_unloading_jvmci()) {
- return false;
- }
-#endif
-
- return unload_nmethod_caches(/*parallel*/true, unloading_occurred);
+static void clean_if_nmethod_is_unloaded(CompiledStaticCall *csc, CompiledMethod* from,
+ bool clean_all) {
+ clean_if_nmethod_is_unloaded(csc, csc->destination(), from, clean_all);
}
// Cleans caches in nmethods that point to either classes that are unloaded
@@ -525,29 +465,70 @@
// nmethods are unloaded. Return postponed=true in the parallel case for
// inline caches found that point to nmethods that are not yet visited during
// the do_unloading walk.
-bool CompiledMethod::unload_nmethod_caches(bool parallel, bool unloading_occurred) {
+void CompiledMethod::unload_nmethod_caches(bool unloading_occurred) {
+ ResourceMark rm;
// Exception cache only needs to be called if unloading occurred
if (unloading_occurred) {
clean_exception_cache();
}
- bool postponed = cleanup_inline_caches_impl(parallel, unloading_occurred, /*clean_all*/false);
+ cleanup_inline_caches_impl(unloading_occurred, false);
// All static stubs need to be cleaned.
clean_ic_stubs();
// Check that the metadata embedded in the nmethod is alive
DEBUG_ONLY(metadata_do(check_class));
+}
- return postponed;
+// The IsUnloadingStruct represents a tuple comprising a result of
+// IsUnloadingBehaviour::is_unloading() for a given unloading cycle.
+struct IsUnloadingStruct {
+ unsigned int _is_unloading:1;
+ unsigned int _unloading_cycle:2;
+};
+
+// The IsUnloadingUnion allows treating the tuple of the IsUnloadingStruct
+// like a uint8_t, making it possible to read and write the tuple atomically.
+union IsUnloadingUnion {
+ IsUnloadingStruct _inflated;
+ uint8_t _value;
+};
+
+bool CompiledMethod::is_unloading() {
+ IsUnloadingUnion state;
+ state._value = RawAccess<MO_RELAXED>::load(&_is_unloading_state);
+ if (state._inflated._is_unloading == 1) {
+ return true;
+ }
+ if (state._inflated._unloading_cycle == CodeCache::unloading_cycle()) {
+ return state._inflated._is_unloading == 1;
+ }
+
+ // The IsUnloadingBehaviour is responsible for checking if there are any dead
+ // oops in the CompiledMethod, by calling oops_do on it.
+ bool result = IsUnloadingBehaviour::current()->is_unloading(this);
+
+ state._inflated._unloading_cycle = CodeCache::unloading_cycle();
+ state._inflated._is_unloading = result ? 1 : 0;
+
+ RawAccess<MO_RELAXED>::store(&_is_unloading_state, state._value);
+
+ return result;
+}
+
+void CompiledMethod::clear_unloading_state() {
+ IsUnloadingUnion state;
+ state._inflated._unloading_cycle = CodeCache::unloading_cycle();
+ state._inflated._is_unloading = 0;
+ RawAccess<MO_RELAXED>::store(&_is_unloading_state, state._value);
}
// Called to clean up after class unloading for live nmethods and from the sweeper
// for all methods.
-bool CompiledMethod::cleanup_inline_caches_impl(bool parallel, bool unloading_occurred, bool clean_all) {
+void CompiledMethod::cleanup_inline_caches_impl(bool unloading_occurred, bool clean_all) {
assert(CompiledICLocker::is_safe(this), "mt unsafe call");
- bool postponed = false;
ResourceMark rm;
// Find all calls in an nmethod and clear the ones that point to non-entrant,
@@ -564,19 +545,18 @@
clean_ic_if_metadata_is_dead(CompiledIC_at(&iter));
}
- postponed |= clean_if_nmethod_is_unloaded(CompiledIC_at(&iter), this, parallel, clean_all);
+ clean_if_nmethod_is_unloaded(CompiledIC_at(&iter), this, clean_all);
break;
case relocInfo::opt_virtual_call_type:
- postponed |= clean_if_nmethod_is_unloaded(CompiledIC_at(&iter), this, parallel, clean_all);
+ clean_if_nmethod_is_unloaded(CompiledIC_at(&iter), this, clean_all);
break;
case relocInfo::static_call_type:
- postponed |= clean_if_nmethod_is_unloaded(compiledStaticCall_at(iter.reloc()), this, parallel, clean_all);
+ clean_if_nmethod_is_unloaded(compiledStaticCall_at(iter.reloc()), this, clean_all);
break;
case relocInfo::oop_type:
- // handled by do_unloading_oops already
break;
case relocInfo::metadata_type:
@@ -586,38 +566,6 @@
break;
}
}
-
- return postponed;
-}
-
-void CompiledMethod::do_unloading_parallel_postponed() {
- ResourceMark rm;
-
- // Make sure the oop's ready to receive visitors
- assert(!is_zombie(),
- "should not call follow on zombie nmethod");
-
- RelocIterator iter(this, oops_reloc_begin());
- while(iter.next()) {
-
- switch (iter.type()) {
-
- case relocInfo::virtual_call_type:
- clean_if_nmethod_is_unloaded(CompiledIC_at(&iter), this, true);
- break;
-
- case relocInfo::opt_virtual_call_type:
- clean_if_nmethod_is_unloaded(CompiledIC_at(&iter), this, true);
- break;
-
- case relocInfo::static_call_type:
- clean_if_nmethod_is_unloaded(compiledStaticCall_at(iter.reloc()), this, true);
- break;
-
- default:
- break;
- }
- }
}
// Iterating over all nmethods, e.g. with the help of CodeCache::nmethods_do(fun) was found
--- a/src/hotspot/share/code/compiledMethod.hpp Thu Nov 01 14:57:26 2018 +0100
+++ b/src/hotspot/share/code/compiledMethod.hpp Fri Nov 02 08:33:59 2018 +0100
@@ -147,6 +147,9 @@
bool _is_far_code; // Code is far from CodeCache.
// Have to use far call instructions to call it from code in CodeCache.
+
+ volatile uint8_t _is_unloading_state; // Local state used to keep track of whether unloading is happening or not
+
// set during construction
unsigned int _has_unsafe_access:1; // May fault due to unsafe access.
unsigned int _has_method_handle_invokes:1; // Has this method MethodHandle invokes?
@@ -202,7 +205,7 @@
virtual address verified_entry_point() const = 0;
virtual void log_identity(xmlStream* log) const = 0;
- virtual void log_state_change(oop cause = NULL) const = 0;
+ virtual void log_state_change() const = 0;
virtual bool make_not_used() = 0;
virtual bool make_not_entrant() = 0;
virtual bool make_entrant() = 0;
@@ -333,17 +336,13 @@
static address get_deopt_original_pc(const frame* fr);
- // GC unloading support
- // Cleans unloaded klasses and unloaded nmethods in inline caches
- bool unload_nmethod_caches(bool parallel, bool class_unloading_occurred);
-
// Inline cache support for class unloading and nmethod unloading
private:
- bool cleanup_inline_caches_impl(bool parallel, bool unloading_occurred, bool clean_all);
+ void cleanup_inline_caches_impl(bool unloading_occurred, bool clean_all);
public:
- bool cleanup_inline_caches(bool clean_all = false) {
+ void cleanup_inline_caches(bool clean_all) {
// Serial version used by sweeper and whitebox test
- return cleanup_inline_caches_impl(false, false, clean_all);
+ cleanup_inline_caches_impl(false, clean_all);
}
virtual void clear_inline_caches();
@@ -373,53 +372,32 @@
virtual void metadata_do(void f(Metadata*)) = 0;
// GC support
-
- void set_unloading_next(CompiledMethod* next) { _unloading_next = next; }
- CompiledMethod* unloading_next() { return _unloading_next; }
-
protected:
address oops_reloc_begin() const;
+
private:
void static clean_ic_if_metadata_is_dead(CompiledIC *ic);
void clean_ic_stubs();
public:
- virtual void do_unloading(BoolObjectClosure* is_alive);
- // The parallel versions are used by G1.
- virtual bool do_unloading_parallel(BoolObjectClosure* is_alive, bool unloading_occurred);
- virtual void do_unloading_parallel_postponed();
-
- static unsigned char global_unloading_clock() { return _global_unloading_clock; }
- static void increase_unloading_clock();
+ // GC unloading support
+ // Cleans unloaded klasses and unloaded nmethods in inline caches
- void set_unloading_clock(unsigned char unloading_clock);
- unsigned char unloading_clock();
+ bool is_unloading();
-protected:
- virtual bool do_unloading_oops(address low_boundary, BoolObjectClosure* is_alive) = 0;
-#if INCLUDE_JVMCI
- virtual bool do_unloading_jvmci() = 0;
-#endif
+ void unload_nmethod_caches(bool class_unloading_occurred);
+ void clear_unloading_state();
+ virtual void do_unloading(bool unloading_occurred) { }
private:
- // GC support to help figure out if an nmethod has been
- // cleaned/unloaded by the current GC.
- static unsigned char _global_unloading_clock;
-
- volatile unsigned char _unloading_clock; // Incremented after GC unloaded/cleaned the nmethod
-
PcDesc* find_pc_desc(address pc, bool approximate) {
return _pc_desc_container.find_pc_desc(pc, approximate, PcDescSearch(code_begin(), scopes_pcs_begin(), scopes_pcs_end()));
}
protected:
- union {
- // Used by G1 to chain nmethods.
- CompiledMethod* _unloading_next;
- // Used by non-G1 GCs to chain nmethods.
- nmethod* _scavenge_root_link; // from CodeCache::scavenge_root_nmethods
- };
+ // Used by some GCs to chain nmethods.
+ nmethod* _scavenge_root_link; // from CodeCache::scavenge_root_nmethods
};
#endif //SHARE_VM_CODE_COMPILEDMETHOD_HPP
--- a/src/hotspot/share/code/nmethod.cpp Thu Nov 01 14:57:26 2018 +0100
+++ b/src/hotspot/share/code/nmethod.cpp Fri Nov 02 08:33:59 2018 +0100
@@ -413,7 +413,6 @@
_oops_do_mark_link = NULL;
_jmethod_id = NULL;
_osr_link = NULL;
- _unloading_next = NULL;
_scavenge_root_link = NULL;
_scavenge_root_state = 0;
#if INCLUDE_RTM_OPT
@@ -599,6 +598,7 @@
code_buffer->copy_code_and_locs_to(this);
code_buffer->copy_values_to(this);
+
if (ScavengeRootsInCode) {
Universe::heap()->register_nmethod(this);
}
@@ -757,6 +757,7 @@
code_buffer->copy_values_to(this);
debug_info->copy_to(this);
dependencies->copy_to(this);
+ clear_unloading_state();
if (ScavengeRootsInCode) {
Universe::heap()->register_nmethod(this);
}
@@ -1025,8 +1026,7 @@
mdo->inc_decompile_count();
}
-void nmethod::make_unloaded(oop cause) {
-
+void nmethod::make_unloaded() {
post_compiled_method_unload();
// This nmethod is being unloaded, make sure that dependencies
@@ -1042,11 +1042,8 @@
LogStream ls(lt);
ls.print("making nmethod " INTPTR_FORMAT
" unloadable, Method*(" INTPTR_FORMAT
- "), cause(" INTPTR_FORMAT ") ",
- p2i(this), p2i(_method), p2i(cause));
- if (cause != NULL) {
- cause->print_value_on(&ls);
- }
+ ") ",
+ p2i(this), p2i(_method));
ls.cr();
}
// Unlink the osr method, so we do not look this up again
@@ -1079,12 +1076,6 @@
// Make the class unloaded - i.e., change state and notify sweeper
assert(SafepointSynchronize::is_at_safepoint(), "must be at safepoint");
- if (is_in_use()) {
- // Transitioning directly from live to unloaded -- so
- // we need to force a cache clean-up; remember this
- // for later on.
- CodeCache::set_needs_cache_clean(true);
- }
// Unregister must be done before the state change
Universe::heap()->unregister_nmethod(this);
@@ -1092,7 +1083,7 @@
_state = unloaded;
// Log the unloading.
- log_state_change(cause);
+ log_state_change();
#if INCLUDE_JVMCI
// The method can only be unloaded after the pointer to the installed code
@@ -1116,7 +1107,7 @@
}
}
-void nmethod::log_state_change(oop cause) const {
+void nmethod::log_state_change() const {
if (LogCompilation) {
if (xtty != NULL) {
ttyLocker ttyl; // keep the following output all in one block
@@ -1129,9 +1120,6 @@
(_state == zombie ? " zombie='1'" : ""));
}
log_identity(xtty);
- if (cause != NULL) {
- xtty->print(" cause='%s'", cause->klass()->external_name());
- }
xtty->stamp();
xtty->end_elem();
}
@@ -1380,21 +1368,6 @@
}
}
-
-// If this oop is not live, the nmethod can be unloaded.
-bool nmethod::can_unload(BoolObjectClosure* is_alive, oop* root) {
- assert(root != NULL, "just checking");
- oop obj = *root;
- if (obj == NULL || is_alive->do_object_b(obj)) {
- return false;
- }
-
- // An nmethod might be unloaded simply because one of its constant oops has gone dead.
- // No actual classes need to be unloaded in order for this to occur.
- make_unloaded(obj);
- return true;
-}
-
// ------------------------------------------------------------------
// post_compiled_method_load_event
// new method for install_code() path
@@ -1468,70 +1441,6 @@
set_unload_reported();
}
-bool nmethod::unload_if_dead_at(RelocIterator* iter_at_oop, BoolObjectClosure *is_alive) {
- assert(iter_at_oop->type() == relocInfo::oop_type, "Wrong relocation type");
-
- oop_Relocation* r = iter_at_oop->oop_reloc();
- // Traverse those oops directly embedded in the code.
- // Other oops (oop_index>0) are seen as part of scopes_oops.
- assert(1 == (r->oop_is_immediate()) +
- (r->oop_addr() >= oops_begin() && r->oop_addr() < oops_end()),
- "oop must be found in exactly one place");
- if (r->oop_is_immediate() && r->oop_value() != NULL) {
- // Unload this nmethod if the oop is dead.
- if (can_unload(is_alive, r->oop_addr())) {
- return true;;
- }
- }
-
- return false;
-}
-
-bool nmethod::do_unloading_scopes(BoolObjectClosure* is_alive) {
- // Scopes
- for (oop* p = oops_begin(); p < oops_end(); p++) {
- if (*p == Universe::non_oop_word()) continue; // skip non-oops
- if (can_unload(is_alive, p)) {
- return true;
- }
- }
- return false;
-}
-
-bool nmethod::do_unloading_oops(address low_boundary, BoolObjectClosure* is_alive) {
- // Compiled code
-
- // Prevent extra code cache walk for platforms that don't have immediate oops.
- if (relocInfo::mustIterateImmediateOopsInCode()) {
- RelocIterator iter(this, low_boundary);
- while (iter.next()) {
- if (iter.type() == relocInfo::oop_type) {
- if (unload_if_dead_at(&iter, is_alive)) {
- return true;
- }
- }
- }
- }
-
- return do_unloading_scopes(is_alive);
-}
-
-#if INCLUDE_JVMCI
-bool nmethod::do_unloading_jvmci() {
- if (_jvmci_installed_code != NULL) {
- if (JNIHandles::is_global_weak_cleared(_jvmci_installed_code)) {
- if (_jvmci_installed_code_triggers_invalidation) {
- // The reference to the installed code has been dropped so invalidate
- // this nmethod and allow the sweeper to reclaim it.
- make_not_entrant();
- }
- clear_jvmci_installed_code();
- }
- }
- return false;
-}
-#endif
-
// Iterate over metadata calling this function. Used by RedefineClasses
void nmethod::metadata_do(void f(Metadata*)) {
{
@@ -1579,6 +1488,34 @@
if (_method != NULL) f(_method);
}
+
+// This is called at the end of the strong tracing/marking phase of a
+// GC to unload an nmethod if it contains otherwise unreachable
+// oops.
+
+void nmethod::do_unloading(bool unloading_occurred) {
+ // Make sure the oop's ready to receive visitors
+ assert(!is_zombie() && !is_unloaded(),
+ "should not call follow on zombie or unloaded nmethod");
+
+ if (is_unloading()) {
+ make_unloaded();
+ } else {
+#if INCLUDE_JVMCI
+ if (_jvmci_installed_code != NULL) {
+ if (JNIHandles::is_global_weak_cleared(_jvmci_installed_code)) {
+ if (_jvmci_installed_code_triggers_invalidation) {
+ make_not_entrant();
+ }
+ clear_jvmci_installed_code();
+ }
+ }
+#endif
+
+ unload_nmethod_caches(unloading_occurred);
+ }
+}
+
void nmethod::oops_do(OopClosure* f, bool allow_zombie) {
// make sure the oops ready to receive visitors
assert(allow_zombie || !is_zombie(), "should not call follow on zombie nmethod");
--- a/src/hotspot/share/code/nmethod.hpp Thu Nov 01 14:57:26 2018 +0100
+++ b/src/hotspot/share/code/nmethod.hpp Fri Nov 02 08:33:59 2018 +0100
@@ -323,6 +323,8 @@
bool is_zombie() const { return _state == zombie; }
bool is_unloaded() const { return _state == unloaded; }
+ virtual void do_unloading(bool unloading_occurred);
+
#if INCLUDE_RTM_OPT
// rtm state accessing and manipulating
RTMState rtm_state() const { return _rtm_state; }
@@ -349,7 +351,7 @@
return _state;
}
- void make_unloaded(oop cause);
+ void make_unloaded();
bool has_dependencies() { return dependencies_size() != 0; }
void flush_dependencies(bool delete_immediately);
@@ -483,20 +485,6 @@
public:
#endif
- protected:
- virtual bool do_unloading_oops(address low_boundary, BoolObjectClosure* is_alive);
-#if INCLUDE_JVMCI
- // See comment for _jvmci_installed_code_triggers_invalidation field.
- // Returns whether this nmethod was unloaded.
- virtual bool do_unloading_jvmci();
-#endif
-
- private:
- bool do_unloading_scopes(BoolObjectClosure* is_alive);
- // Unload a nmethod if the *root object is dead.
- bool can_unload(BoolObjectClosure* is_alive, oop* root);
- bool unload_if_dead_at(RelocIterator *iter_at_oop, BoolObjectClosure* is_alive);
-
public:
void oops_do(OopClosure* f) { oops_do(f, false); }
void oops_do(OopClosure* f, bool allow_zombie);
@@ -555,7 +543,7 @@
// Logging
void log_identity(xmlStream* log) const;
void log_new_nmethod() const;
- void log_state_change(oop cause = NULL) const;
+ void log_state_change() const;
// Prints block-level comments, including nmethod specific block labels:
virtual void print_block_comment(outputStream* stream, address block_begin) const {
--- a/src/hotspot/share/gc/g1/g1CollectedHeap.cpp Thu Nov 01 14:57:26 2018 +0100
+++ b/src/hotspot/share/gc/g1/g1CollectedHeap.cpp Fri Nov 02 08:33:59 2018 +0100
@@ -62,6 +62,7 @@
#include "gc/g1/heapRegionSet.inline.hpp"
#include "gc/g1/vm_operations_g1.hpp"
#include "gc/shared/adaptiveSizePolicy.hpp"
+#include "gc/shared/gcBehaviours.hpp"
#include "gc/shared/gcHeapSummary.hpp"
#include "gc/shared/gcId.hpp"
#include "gc/shared/gcLocker.hpp"
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/hotspot/share/gc/shared/gcBehaviours.cpp Fri Nov 02 08:33:59 2018 +0100
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 2018, 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.
+ *
+ */
+
+#include "precompiled.hpp"
+#include "code/compiledMethod.hpp"
+#include "code/nmethod.hpp"
+#include "gc/shared/gcBehaviours.hpp"
+
+IsUnloadingBehaviour* IsUnloadingBehaviour::_current = NULL;
+
+class IsCompiledMethodUnloadingOopClosure: public OopClosure {
+ BoolObjectClosure *_cl;
+ bool _is_unloading;
+
+public:
+ IsCompiledMethodUnloadingOopClosure(BoolObjectClosure* cl)
+ : _cl(cl),
+ _is_unloading(false)
+ { }
+
+ virtual void do_oop(oop* p) {
+ if (_is_unloading) {
+ return;
+ }
+ oop obj = *p;
+ if (obj == NULL) {
+ return;
+ }
+ if (!_cl->do_object_b(obj)) {
+ _is_unloading = true;
+ }
+ }
+
+ virtual void do_oop(narrowOop* p) {
+ ShouldNotReachHere();
+ }
+
+ bool is_unloading() const {
+ return _is_unloading;
+ }
+};
+
+bool ClosureIsUnloadingBehaviour::is_unloading(CompiledMethod* cm) const {
+ if (cm->is_nmethod()) {
+ IsCompiledMethodUnloadingOopClosure cl(_cl);
+ static_cast<nmethod*>(cm)->oops_do(&cl);
+ return cl.is_unloading();
+ } else {
+ return false;
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/hotspot/share/gc/shared/gcBehaviours.hpp Fri Nov 02 08:33:59 2018 +0100
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2018, 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.
+ *
+ */
+
+#ifndef SHARE_GC_SHARED_BEHAVIOURS_HPP
+#define SHARE_GC_SHARED_BEHAVIOURS_HPP
+
+#include "memory/iterator.hpp"
+#include "oops/oopsHierarchy.hpp"
+
+// This is the behaviour for checking if a CompiledMethod is unloading
+// or has unloaded due to having phantomly dead oops in it after a GC.
+class IsUnloadingBehaviour {
+ static IsUnloadingBehaviour* _current;
+
+public:
+ virtual bool is_unloading(CompiledMethod* cm) const = 0;
+ static IsUnloadingBehaviour* current() { return _current; }
+ static void set_current(IsUnloadingBehaviour* current) { _current = current; }
+};
+
+class ClosureIsUnloadingBehaviour: public IsUnloadingBehaviour {
+ BoolObjectClosure *const _cl;
+
+public:
+ ClosureIsUnloadingBehaviour(BoolObjectClosure* is_alive)
+ : _cl(is_alive)
+ { }
+
+ virtual bool is_unloading(CompiledMethod* cm) const;
+};
+
+#endif // SHARE_GC_SHARED_BEHAVIOURS_HPP
--- a/src/hotspot/share/gc/shared/parallelCleaning.cpp Thu Nov 01 14:57:26 2018 +0100
+++ b/src/hotspot/share/gc/shared/parallelCleaning.cpp Fri Nov 02 08:33:59 2018 +0100
@@ -27,6 +27,7 @@
#include "classfile/stringTable.hpp"
#include "code/codeCache.hpp"
#include "gc/shared/parallelCleaning.hpp"
+#include "logging/log.hpp"
#include "memory/resourceArea.hpp"
#include "logging/log.hpp"
@@ -67,14 +68,11 @@
}
CodeCacheUnloadingTask::CodeCacheUnloadingTask(uint num_workers, BoolObjectClosure* is_alive, bool unloading_occurred) :
- _is_alive(is_alive),
+ _unloading_scope(is_alive),
_unloading_occurred(unloading_occurred),
_num_workers(num_workers),
_first_nmethod(NULL),
- _claimed_nmethod(NULL),
- _postponed_list(NULL),
- _num_entered_barrier(0) {
- CompiledMethod::increase_unloading_clock();
+ _claimed_nmethod(NULL) {
// Get first alive nmethod
CompiledMethodIterator iter = CompiledMethodIterator();
if(iter.next_alive()) {
@@ -86,7 +84,6 @@
CodeCacheUnloadingTask::~CodeCacheUnloadingTask() {
CodeCache::verify_clean_inline_caches();
- CodeCache::set_needs_cache_clean(false);
guarantee(CodeCache::scavenge_root_nmethods() == NULL, "Must be");
CodeCache::verify_icholder_relocations();
@@ -94,31 +91,6 @@
Monitor* CodeCacheUnloadingTask::_lock = new Monitor(Mutex::leaf, "Code Cache Unload lock", false, Monitor::_safepoint_check_never);
-void CodeCacheUnloadingTask::add_to_postponed_list(CompiledMethod* nm) {
- CompiledMethod* old;
- do {
- old = _postponed_list;
- nm->set_unloading_next(old);
- } while (Atomic::cmpxchg(nm, &_postponed_list, old) != old);
-}
-
-void CodeCacheUnloadingTask::clean_nmethod(CompiledMethod* nm) {
- bool postponed = nm->do_unloading_parallel(_is_alive, _unloading_occurred);
-
- if (postponed) {
- // This nmethod referred to an nmethod that has not been cleaned/unloaded yet.
- add_to_postponed_list(nm);
- }
-
- // Mark that this nmethod has been cleaned/unloaded.
- // After this call, it will be safe to ask if this nmethod was unloaded or not.
- nm->set_unloading_clock(CompiledMethod::global_unloading_clock());
-}
-
-void CodeCacheUnloadingTask::clean_nmethod_postponed(CompiledMethod* nm) {
- nm->do_unloading_parallel_postponed();
-}
-
void CodeCacheUnloadingTask::claim_nmethods(CompiledMethod** claimed_nmethods, int *num_claimed_nmethods) {
CompiledMethod* first;
CompiledMethodIterator last;
@@ -143,44 +115,10 @@
} while (Atomic::cmpxchg(last.method(), &_claimed_nmethod, first) != first);
}
-CompiledMethod* CodeCacheUnloadingTask::claim_postponed_nmethod() {
- CompiledMethod* claim;
- CompiledMethod* next;
-
- do {
- claim = _postponed_list;
- if (claim == NULL) {
- return NULL;
- }
-
- next = claim->unloading_next();
-
- } while (Atomic::cmpxchg(next, &_postponed_list, claim) != claim);
-
- return claim;
-}
-
-void CodeCacheUnloadingTask::barrier_mark(uint worker_id) {
- MonitorLockerEx ml(_lock, Mutex::_no_safepoint_check_flag);
- _num_entered_barrier++;
- if (_num_entered_barrier == _num_workers) {
- ml.notify_all();
- }
-}
-
-void CodeCacheUnloadingTask::barrier_wait(uint worker_id) {
- if (_num_entered_barrier < _num_workers) {
- MonitorLockerEx ml(_lock, Mutex::_no_safepoint_check_flag);
- while (_num_entered_barrier < _num_workers) {
- ml.wait(Mutex::_no_safepoint_check_flag, 0, false);
- }
- }
-}
-
-void CodeCacheUnloadingTask::work_first_pass(uint worker_id) {
+void CodeCacheUnloadingTask::work(uint worker_id) {
// The first nmethods is claimed by the first worker.
if (worker_id == 0 && _first_nmethod != NULL) {
- clean_nmethod(_first_nmethod);
+ _first_nmethod->do_unloading(_unloading_occurred);
_first_nmethod = NULL;
}
@@ -195,19 +133,11 @@
}
for (int i = 0; i < num_claimed_nmethods; i++) {
- clean_nmethod(claimed_nmethods[i]);
+ claimed_nmethods[i]->do_unloading(_unloading_occurred);
}
}
}
-void CodeCacheUnloadingTask::work_second_pass(uint worker_id) {
- CompiledMethod* nm;
- // Take care of postponed nmethods.
- while ((nm = claim_postponed_nmethod()) != NULL) {
- clean_nmethod_postponed(nm);
- }
-}
-
KlassCleaningTask::KlassCleaningTask() :
_clean_klass_tree_claimed(0),
_klass_iterator() {
@@ -257,21 +187,11 @@
// The parallel work done by all worker threads.
void ParallelCleaningTask::work(uint worker_id) {
- // Do first pass of code cache cleaning.
- _code_cache_task.work_first_pass(worker_id);
-
- // Let the threads mark that the first pass is done.
- _code_cache_task.barrier_mark(worker_id);
+ // Do first pass of code cache cleaning.
+ _code_cache_task.work(worker_id);
- // Clean the Strings and Symbols.
- _string_task.work(worker_id);
-
- // Wait for all workers to finish the first code cache cleaning pass.
- _code_cache_task.barrier_wait(worker_id);
-
- // Do the second code cache cleaning work, which realize on
- // the liveness information gathered during the first pass.
- _code_cache_task.work_second_pass(worker_id);
+ // Clean the Strings and Symbols.
+ _string_task.work(worker_id);
// Clean all klasses that were not unloaded.
// The weak metadata in klass doesn't need to be
--- a/src/hotspot/share/gc/shared/parallelCleaning.hpp Thu Nov 01 14:57:26 2018 +0100
+++ b/src/hotspot/share/gc/shared/parallelCleaning.hpp Fri Nov 02 08:33:59 2018 +0100
@@ -56,46 +56,27 @@
};
class CodeCacheUnloadingTask {
-private:
static Monitor* _lock;
- BoolObjectClosure* const _is_alive;
- const bool _unloading_occurred;
- const uint _num_workers;
+ CodeCache::UnloadingScope _unloading_scope;
+ const bool _unloading_occurred;
+ const uint _num_workers;
// Variables used to claim nmethods.
CompiledMethod* _first_nmethod;
CompiledMethod* volatile _claimed_nmethod;
- // The list of nmethods that need to be processed by the second pass.
- CompiledMethod* volatile _postponed_list;
- volatile uint _num_entered_barrier;
-
public:
CodeCacheUnloadingTask(uint num_workers, BoolObjectClosure* is_alive, bool unloading_occurred);
~CodeCacheUnloadingTask();
private:
- void add_to_postponed_list(CompiledMethod* nm);
- void clean_nmethod(CompiledMethod* nm);
- void clean_nmethod_postponed(CompiledMethod* nm);
+ static const int MaxClaimNmethods = 16;
+ void claim_nmethods(CompiledMethod** claimed_nmethods, int *num_claimed_nmethods);
- static const int MaxClaimNmethods = 16;
-
- void claim_nmethods(CompiledMethod** claimed_nmethods, int *num_claimed_nmethods);
- CompiledMethod* claim_postponed_nmethod();
public:
- // Mark that we're done with the first pass of nmethod cleaning.
- void barrier_mark(uint worker_id);
-
- // See if we have to wait for the other workers to
- // finish their first-pass nmethod cleaning work.
- void barrier_wait(uint worker_id);
-
- // Cleaning and unloading of nmethods. Some work has to be postponed
- // to the second pass, when we know which nmethods survive.
- void work_first_pass(uint worker_id);
- void work_second_pass(uint worker_id);
+ // Cleaning and unloading of nmethods.
+ void work(uint worker_id);
};
--- a/src/hotspot/share/runtime/sweeper.cpp Thu Nov 01 14:57:26 2018 +0100
+++ b/src/hotspot/share/runtime/sweeper.cpp Fri Nov 02 08:33:59 2018 +0100
@@ -702,7 +702,7 @@
if (cm->is_alive()) {
// Clean inline caches that point to zombie/non-entrant/unloaded nmethods
CompiledICLocker ml(cm);
- cm->cleanup_inline_caches();
+ cm->cleanup_inline_caches(false);
SWEEP(cm);
}
return result;
@@ -748,7 +748,7 @@
} else {
// Still alive, clean up its inline caches
CompiledICLocker ml(cm);
- cm->cleanup_inline_caches();
+ cm->cleanup_inline_caches(false);
SWEEP(cm);
}
} else if (cm->is_unloaded()) {
@@ -758,7 +758,7 @@
// Clean ICs of unloaded nmethods as well because they may reference other
// unloaded nmethods that may be flushed earlier in the sweeper cycle.
CompiledICLocker ml(cm);
- cm->cleanup_inline_caches();
+ cm->cleanup_inline_caches(false);
}
if (cm->is_osr_method()) {
SWEEP(cm);
@@ -779,7 +779,7 @@
}
// Clean inline caches that point to zombie/non-entrant/unloaded nmethods
CompiledICLocker ml(cm);
- cm->cleanup_inline_caches();
+ cm->cleanup_inline_caches(false);
SWEEP(cm);
}
return result;