8023191: OSR nmethods should be flushed to free space in CodeCache

Treat OSR nmethods like normal nmethods and flush them if they are cold/unused.

Reviewed-by: kvn
This commit is contained in:
Tobias Hartmann 2016-03-18 09:32:29 +01:00
parent 2b48dbfd93
commit ba7e4da495
6 changed files with 90 additions and 50 deletions

View File

@ -1387,9 +1387,17 @@ void nmethod::make_unloaded(BoolObjectClosure* is_alive, oop cause) {
void nmethod::invalidate_osr_method() {
assert(_entry_bci != InvocationEntryBci, "wrong kind of nmethod");
#ifndef ASSERT
// Make sure osr nmethod is invalidated only once
if (!is_in_use()) {
return;
}
#endif
// Remove from list of active nmethods
if (method() != NULL)
method()->method_holder()->remove_osr_nmethod(this);
if (method() != NULL) {
bool removed = method()->method_holder()->remove_osr_nmethod(this);
assert(!removed || is_in_use(), "unused osr nmethod should be invalidated");
}
}
void nmethod::log_state_change() const {
@ -1510,7 +1518,7 @@ bool nmethod::make_not_entrant_or_zombie(unsigned int state) {
// happens or they get unloaded.
if (state == zombie) {
{
// Flushing dependecies must be done before any possible
// Flushing dependencies must be done before any possible
// safepoint can sneak in, otherwise the oops used by the
// dependency logic could have become stale.
MutexLockerEx mu(CodeCache_lock, Mutex::_no_safepoint_check_flag);
@ -1526,7 +1534,7 @@ bool nmethod::make_not_entrant_or_zombie(unsigned int state) {
// zombie only - if a JVMTI agent has enabled the CompiledMethodUnload
// event and it hasn't already been reported for this nmethod then
// report it now. The event may have been reported earilier if the GC
// report it now. The event may have been reported earlier if the GC
// marked it for unloading). JvmtiDeferredEventQueue support means
// we no longer go to a safepoint here.
post_compiled_method_unload();
@ -1554,8 +1562,10 @@ bool nmethod::make_not_entrant_or_zombie(unsigned int state) {
void nmethod::flush() {
// Note that there are no valid oops in the nmethod anymore.
assert(is_zombie() || (is_osr_method() && is_unloaded()), "must be a zombie method");
assert(is_marked_for_reclamation() || (is_osr_method() && is_unloaded()), "must be marked for reclamation");
assert(!is_osr_method() || is_unloaded() || is_zombie(),
"osr nmethod must be unloaded or zombie before flushing");
assert(is_zombie() || is_osr_method(), "must be a zombie method");
assert(is_marked_for_reclamation() || is_osr_method(), "must be marked for reclamation");
assert (!is_locked_by_vm(), "locked methods shouldn't be flushed");
assert_locked_or_safepoint(CodeCache_lock);
@ -1563,9 +1573,9 @@ void nmethod::flush() {
// completely deallocate this method
Events::log(JavaThread::current(), "flushing nmethod " INTPTR_FORMAT, p2i(this));
if (PrintMethodFlushing) {
tty->print_cr("*flushing nmethod %3d/" INTPTR_FORMAT ". Live blobs:" UINT32_FORMAT
tty->print_cr("*flushing %s nmethod %3d/" INTPTR_FORMAT ". Live blobs:" UINT32_FORMAT
"/Free CodeCache:" SIZE_FORMAT "Kb",
_compile_id, p2i(this), CodeCache::blob_count(),
is_osr_method() ? "osr" : "",_compile_id, p2i(this), CodeCache::blob_count(),
CodeCache::unallocated_capacity(CodeCache::get_code_blob_type(this))/1024);
}
@ -2917,10 +2927,7 @@ void nmethod::print() const {
tty->print("((nmethod*) " INTPTR_FORMAT ") ", p2i(this));
tty->print(" for method " INTPTR_FORMAT , p2i(method()));
tty->print(" { ");
if (is_in_use()) tty->print("in_use ");
if (is_not_entrant()) tty->print("not_entrant ");
if (is_zombie()) tty->print("zombie ");
if (is_unloaded()) tty->print("unloaded ");
tty->print_cr("%s ", state());
if (on_scavenge_root_list()) tty->print("scavenge_root ");
tty->print_cr("}:");
}

View File

@ -207,7 +207,7 @@ class nmethod : public CodeBlob {
unsigned int _has_wide_vectors:1; // Preserve wide vectors at safepoints
// Protected by Patching_lock
volatile unsigned char _state; // {alive, not_entrant, zombie, unloaded}
volatile unsigned char _state; // {in_use, not_entrant, zombie, unloaded}
volatile unsigned char _unloading_clock; // Incremented after GC unloaded/cleaned the nmethod
@ -438,7 +438,20 @@ class nmethod : public CodeBlob {
bool is_alive() const { return _state == in_use || _state == not_entrant; }
bool is_not_entrant() const { return _state == not_entrant; }
bool is_zombie() const { return _state == zombie; }
bool is_unloaded() const { return _state == unloaded; }
bool is_unloaded() const { return _state == unloaded; }
// returns a string version of the nmethod state
const char* state() const {
switch(_state) {
case in_use: return "in use";
case not_entrant: return "not_entrant";
case zombie: return "zombie";
case unloaded: return "unloaded";
default:
fatal("unexpected nmethod state: %d", _state);
return NULL;
}
}
#if INCLUDE_RTM_OPT
// rtm state accessing and manipulating

View File

@ -2523,8 +2523,8 @@ void InstanceKlass::add_osr_nmethod(nmethod* n) {
}
}
void InstanceKlass::remove_osr_nmethod(nmethod* n) {
// Remove osr nmethod from the list. Return true if found and removed.
bool InstanceKlass::remove_osr_nmethod(nmethod* n) {
// This is a short non-blocking critical region, so the no safepoint check is ok.
MutexLockerEx ml(OsrList_lock, Mutex::_no_safepoint_check_flag);
assert(n->is_osr_method(), "wrong kind of nmethod");
@ -2533,6 +2533,7 @@ void InstanceKlass::remove_osr_nmethod(nmethod* n) {
int max_level = CompLevel_none; // Find the max comp level excluding n
Method* m = n->method();
// Search for match
bool found = false;
while(cur != NULL && cur != n) {
if (TieredCompilation && m == cur->method()) {
// Find max level before n
@ -2543,6 +2544,7 @@ void InstanceKlass::remove_osr_nmethod(nmethod* n) {
}
nmethod* next = NULL;
if (cur == n) {
found = true;
next = cur->osr_link();
if (last == NULL) {
// Remove first element
@ -2563,6 +2565,7 @@ void InstanceKlass::remove_osr_nmethod(nmethod* n) {
}
m->set_highest_osr_comp_level(max_level);
}
return found;
}
int InstanceKlass::mark_osr_nmethods(const Method* m) {

View File

@ -829,7 +829,7 @@ public:
nmethod* osr_nmethods_head() const { return _osr_nmethods_head; };
void set_osr_nmethods_head(nmethod* h) { _osr_nmethods_head = h; };
void add_osr_nmethod(nmethod* n);
void remove_osr_nmethod(nmethod* n);
bool remove_osr_nmethod(nmethod* n);
int mark_osr_nmethods(const Method* m);
nmethod* lookup_osr_nmethod(const Method* m, int bci, int level, bool match_level) const;

View File

@ -355,8 +355,8 @@ void NMethodSweeper::possibly_sweep() {
bool forced = _force_sweep;
// Force stack scanning if there is only 10% free space in the code cache.
// We force stack scanning only non-profiled code heap gets full, since critical
// allocation go to the non-profiled heap and we must be make sure that there is
// We force stack scanning only if the non-profiled code heap gets full, since critical
// allocations go to the non-profiled heap and we must be make sure that there is
// enough space.
double free_percent = 1 / CodeCache::reverse_free_ratio(CodeBlobType::MethodNonProfiled) * 100;
if (free_percent <= StartAggressiveSweepingAt) {
@ -423,12 +423,19 @@ void NMethodSweeper::sweep_code_cache() {
// Now ready to process nmethod and give up CodeCache_lock
{
MutexUnlockerEx mu(CodeCache_lock, Mutex::_no_safepoint_check_flag);
// Save information before potentially flushing the nmethod
int size = nm->total_size();
bool is_c2_method = nm->is_compiled_by_c2();
bool is_osr = nm->is_osr_method();
int compile_id = nm->compile_id();
intptr_t address = p2i(nm);
const char* state_before = nm->state();
const char* state_after = "";
MethodStateChange type = process_nmethod(nm);
switch (type) {
case Flushed:
state_after = "flushed";
freed_memory += size;
++flushed_count;
if (is_c2_method) {
@ -436,9 +443,11 @@ void NMethodSweeper::sweep_code_cache() {
}
break;
case MarkedForReclamation:
state_after = "marked for reclamation";
++marked_for_reclamation_count;
break;
case MadeZombie:
state_after = "made zombie";
++zombified_count;
break;
case None:
@ -446,7 +455,11 @@ void NMethodSweeper::sweep_code_cache() {
default:
ShouldNotReachHere();
}
if (PrintMethodFlushing && Verbose && type != None) {
tty->print_cr("### %s nmethod %3d/" PTR_FORMAT " (%s) %s", is_osr ? "osr" : "", compile_id, address, state_before, state_after);
}
}
_seen++;
handle_safepoint_request();
}
@ -533,7 +546,7 @@ class NMethodMarker: public StackObj {
NMethodMarker(nmethod* nm) {
JavaThread* current = JavaThread::current();
assert (current->is_Code_cache_sweeper_thread(), "Must be");
_thread = (CodeCacheSweeperThread*)JavaThread::current();
_thread = (CodeCacheSweeperThread*)current;
if (!nm->is_zombie() && !nm->is_unloaded()) {
// Only expose live nmethods for scanning
_thread->set_scanned_nmethod(nm);
@ -545,6 +558,10 @@ class NMethodMarker: public StackObj {
};
void NMethodSweeper::release_nmethod(nmethod* nm) {
// Make sure the released nmethod is no longer referenced by the sweeper thread
CodeCacheSweeperThread* thread = (CodeCacheSweeperThread*)JavaThread::current();
thread->set_scanned_nmethod(NULL);
// Clean up any CompiledICHolders
{
ResourceMark rm;
@ -575,7 +592,7 @@ NMethodSweeper::MethodStateChange NMethodSweeper::process_nmethod(nmethod* nm) {
if (nm->is_locked_by_vm()) {
// But still remember to clean-up inline caches for alive nmethods
if (nm->is_alive()) {
// Clean inline caches that point to zombie/non-entrant methods
// Clean inline caches that point to zombie/non-entrant/unloaded nmethods
MutexLocker cl(CompiledIC_lock);
nm->cleanup_inline_caches();
SWEEP(nm);
@ -589,42 +606,44 @@ NMethodSweeper::MethodStateChange NMethodSweeper::process_nmethod(nmethod* nm) {
// there are no inline caches that refer to it.
if (nm->is_marked_for_reclamation()) {
assert(!nm->is_locked_by_vm(), "must not flush locked nmethods");
if (PrintMethodFlushing && Verbose) {
tty->print_cr("### Nmethod %3d/" PTR_FORMAT " (marked for reclamation) being flushed", nm->compile_id(), p2i(nm));
}
release_nmethod(nm);
assert(result == None, "sanity");
result = Flushed;
} else {
if (PrintMethodFlushing && Verbose) {
tty->print_cr("### Nmethod %3d/" PTR_FORMAT " (zombie) being marked for reclamation", nm->compile_id(), p2i(nm));
}
nm->mark_for_reclamation();
// Keep track of code cache state change
_bytes_changed += nm->total_size();
SWEEP(nm);
assert(result == None, "sanity");
result = MarkedForReclamation;
assert(nm->is_marked_for_reclamation(), "nmethod must be marked for reclamation");
}
} else if (nm->is_not_entrant()) {
// If there are no current activations of this method on the
// stack we can safely convert it to a zombie method
if (nm->can_convert_to_zombie()) {
// Clear ICStubs to prevent back patching stubs of zombie or unloaded
// Clear ICStubs to prevent back patching stubs of zombie or flushed
// nmethods during the next safepoint (see ICStub::finalize).
{
MutexLocker cl(CompiledIC_lock);
nm->clear_ic_stubs();
}
if (PrintMethodFlushing && Verbose) {
tty->print_cr("### Nmethod %3d/" PTR_FORMAT " (not entrant) being made zombie", nm->compile_id(), p2i(nm));
}
// Code cache state change is tracked in make_zombie()
nm->make_zombie();
SWEEP(nm);
assert(result == None, "sanity");
result = MadeZombie;
assert(nm->is_zombie(), "nmethod must be zombie");
if (nm->is_osr_method()) {
// No inline caches will ever point to osr methods, so we can just remove it.
// Make sure that we unregistered the nmethod with the heap and flushed all
// dependencies before removing the nmethod (done in make_zombie()).
assert(nm->is_zombie(), "nmethod must be unregistered");
release_nmethod(nm);
assert(result == None, "sanity");
result = Flushed;
} else {
assert(result == None, "sanity");
result = MadeZombie;
assert(nm->is_zombie(), "nmethod must be zombie");
}
} else {
// Still alive, clean up its inline caches
MutexLocker cl(CompiledIC_lock);
@ -632,9 +651,13 @@ NMethodSweeper::MethodStateChange NMethodSweeper::process_nmethod(nmethod* nm) {
SWEEP(nm);
}
} else if (nm->is_unloaded()) {
// Unloaded code, just make it a zombie
if (PrintMethodFlushing && Verbose) {
tty->print_cr("### Nmethod %3d/" PTR_FORMAT " (unloaded) being made zombie", nm->compile_id(), p2i(nm));
// Code is unloaded, so there are no activations on the stack.
// Convert the nmethod to zombie or flush it directly in the OSR case.
{
// Clean ICs of unloaded nmethods as well because they may reference other
// unloaded nmethods that may be flushed earlier in the sweeper cycle.
MutexLocker cl(CompiledIC_lock);
nm->cleanup_inline_caches();
}
if (nm->is_osr_method()) {
SWEEP(nm);
@ -643,12 +666,6 @@ NMethodSweeper::MethodStateChange NMethodSweeper::process_nmethod(nmethod* nm) {
assert(result == None, "sanity");
result = Flushed;
} else {
{
// Clean ICs of unloaded nmethods as well because they may reference other
// unloaded nmethods that may be flushed earlier in the sweeper cycle.
MutexLocker cl(CompiledIC_lock);
nm->cleanup_inline_caches();
}
// Code cache state change is tracked in make_zombie()
nm->make_zombie();
SWEEP(nm);
@ -657,7 +674,7 @@ NMethodSweeper::MethodStateChange NMethodSweeper::process_nmethod(nmethod* nm) {
}
} else {
possibly_flush(nm);
// Clean-up all inline caches that point to zombie/non-reentrant methods
// Clean inline caches that point to zombie/non-entrant/unloaded nmethods
MutexLocker cl(CompiledIC_lock);
nm->cleanup_inline_caches();
SWEEP(nm);
@ -668,10 +685,10 @@ NMethodSweeper::MethodStateChange NMethodSweeper::process_nmethod(nmethod* nm) {
void NMethodSweeper::possibly_flush(nmethod* nm) {
if (UseCodeCacheFlushing) {
if (!nm->is_locked_by_vm() && !nm->is_osr_method() && !nm->is_native_method()) {
if (!nm->is_locked_by_vm() && !nm->is_native_method()) {
bool make_not_entrant = false;
// Do not make native methods and OSR-methods not-entrant
// Do not make native methods not-entrant
nm->dec_hotness_counter();
// Get the initial value of the hotness counter. This value depends on the
// ReservedCodeCacheSize

View File

@ -45,12 +45,12 @@ class WhiteBox;
// and sweep_code_cache() cannot execute at the same time.
// To reclaim memory, nmethods are first marked as 'not-entrant'. Methods can
// be made not-entrant by (i) the sweeper, (ii) deoptimization, (iii) dependency
// invalidation, and (iv) being replaced be a different method version (tiered
// compilation). Not-entrant nmethod cannot be called by Java threads, but they
// can still be active on the stack. To ensure that active nmethod are not reclaimed,
// invalidation, and (iv) being replaced by a different method version (tiered
// compilation). Not-entrant nmethods cannot be called by Java threads, but they
// can still be active on the stack. To ensure that active nmethods are not reclaimed,
// we have to wait until the next marking phase has completed. If a not-entrant
// nmethod was NOT marked as active, it can be converted to 'zombie' state. To safely
// remove the nmethod, all inline caches (IC) that point to the the nmethod must be
// remove the nmethod, all inline caches (IC) that point to the nmethod must be
// cleared. After that, the nmethod can be evicted from the code cache. Each nmethod's
// state change happens during separate sweeps. It may take at least 3 sweeps before an
// nmethod's space is freed.