# HG changeset patch # User eosterlund # Date 1541144039 -3600 # Node ID 5c679ec60888c54daeba5e6e4666f6a05326fe2f # Parent d6dc479bcdd386e595efd1c5798753cc6805ccee 8209189: Make CompiledMethod::do_unloading more concurrent Reviewed-by: kvn, coleenp diff -r d6dc479bcdd3 -r 5c679ec60888 src/hotspot/share/aot/aotCodeHeap.cpp --- 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); } } diff -r d6dc479bcdd3 -r 5c679ec60888 src/hotspot/share/aot/aotCompiledMethod.cpp --- 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) { diff -r d6dc479bcdd3 -r 5c679ec60888 src/hotspot/share/aot/aotCompiledMethod.hpp --- 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 { diff -r d6dc479bcdd3 -r 5c679ec60888 src/hotspot/share/code/codeCache.cpp --- 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() { diff -r d6dc479bcdd3 -r 5c679ec60888 src/hotspot/share/code/codeCache.hpp --- 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); diff -r d6dc479bcdd3 -r 5c679ec60888 src/hotspot/share/code/compiledMethod.cpp --- 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 -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::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::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::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 diff -r d6dc479bcdd3 -r 5c679ec60888 src/hotspot/share/code/compiledMethod.hpp --- 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 diff -r d6dc479bcdd3 -r 5c679ec60888 src/hotspot/share/code/nmethod.cpp --- 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"); diff -r d6dc479bcdd3 -r 5c679ec60888 src/hotspot/share/code/nmethod.hpp --- 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 { diff -r d6dc479bcdd3 -r 5c679ec60888 src/hotspot/share/gc/g1/g1CollectedHeap.cpp --- 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" diff -r d6dc479bcdd3 -r 5c679ec60888 src/hotspot/share/gc/shared/gcBehaviours.cpp --- /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(cm)->oops_do(&cl); + return cl.is_unloading(); + } else { + return false; + } +} diff -r d6dc479bcdd3 -r 5c679ec60888 src/hotspot/share/gc/shared/gcBehaviours.hpp --- /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 diff -r d6dc479bcdd3 -r 5c679ec60888 src/hotspot/share/gc/shared/parallelCleaning.cpp --- 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 diff -r d6dc479bcdd3 -r 5c679ec60888 src/hotspot/share/gc/shared/parallelCleaning.hpp --- 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); }; diff -r d6dc479bcdd3 -r 5c679ec60888 src/hotspot/share/runtime/sweeper.cpp --- 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;