8227745: Enable Escape Analysis for Better Performance in the Presence of JVMTI Agents
8233915: JVMTI FollowReferences: Java Heap Leak not found because of C2 Scalar Replacement Reviewed-by: mdoerr, goetz, sspitsyn, kvn
This commit is contained in:
parent
f167a71f1d
commit
40f847e2fb
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 1999, 2019, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 1999, 2020, 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
|
||||
@ -244,7 +244,11 @@ class IRScopeDebugInfo: public CompilationResourceObj {
|
||||
bool reexecute = topmost ? should_reexecute() : false;
|
||||
bool return_oop = false; // This flag will be ignored since it used only for C2 with escape analysis.
|
||||
bool rethrow_exception = false;
|
||||
recorder->describe_scope(pc_offset, methodHandle(), scope()->method(), bci(), reexecute, rethrow_exception, is_method_handle_invoke, return_oop, locvals, expvals, monvals);
|
||||
bool has_ea_local_in_scope = false;
|
||||
bool arg_escape = false;
|
||||
recorder->describe_scope(pc_offset, methodHandle(), scope()->method(), bci(),
|
||||
reexecute, rethrow_exception, is_method_handle_invoke, return_oop,
|
||||
has_ea_local_in_scope, arg_escape, locvals, expvals, monvals);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -242,6 +242,7 @@ bool ciEnv::cache_jvmti_state() {
|
||||
_jvmti_can_post_on_exceptions = JvmtiExport::can_post_on_exceptions();
|
||||
_jvmti_can_pop_frame = JvmtiExport::can_pop_frame();
|
||||
_jvmti_can_get_owned_monitor_info = JvmtiExport::can_get_owned_monitor_info();
|
||||
_jvmti_can_walk_any_space = JvmtiExport::can_walk_any_space();
|
||||
return _task != NULL && _task->method()->is_old();
|
||||
}
|
||||
|
||||
@ -271,6 +272,10 @@ bool ciEnv::jvmti_state_changed() const {
|
||||
JvmtiExport::can_get_owned_monitor_info()) {
|
||||
return true;
|
||||
}
|
||||
if (!_jvmti_can_walk_any_space &&
|
||||
JvmtiExport::can_walk_any_space()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
@ -74,6 +74,7 @@ private:
|
||||
bool _jvmti_can_post_on_exceptions;
|
||||
bool _jvmti_can_pop_frame;
|
||||
bool _jvmti_can_get_owned_monitor_info; // includes can_get_owned_monitor_stack_depth_info
|
||||
bool _jvmti_can_walk_any_space;
|
||||
|
||||
// Cache DTrace flags
|
||||
bool _dtrace_extended_probes;
|
||||
@ -349,6 +350,7 @@ public:
|
||||
bool jvmti_can_hotswap_or_post_breakpoint() const { return _jvmti_can_hotswap_or_post_breakpoint; }
|
||||
bool jvmti_can_post_on_exceptions() const { return _jvmti_can_post_on_exceptions; }
|
||||
bool jvmti_can_get_owned_monitor_info() const { return _jvmti_can_get_owned_monitor_info; }
|
||||
bool jvmti_can_walk_any_space() const { return _jvmti_can_walk_any_space; }
|
||||
|
||||
// Cache DTrace flags
|
||||
void cache_dtrace_flags();
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2015, 2019, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2015, 2020, 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
|
||||
@ -293,17 +293,13 @@ void CompiledMethod::verify_oop_relocations() {
|
||||
ScopeDesc* CompiledMethod::scope_desc_at(address pc) {
|
||||
PcDesc* pd = pc_desc_at(pc);
|
||||
guarantee(pd != NULL, "scope must be present");
|
||||
return new ScopeDesc(this, pd->scope_decode_offset(),
|
||||
pd->obj_decode_offset(), pd->should_reexecute(), pd->rethrow_exception(),
|
||||
pd->return_oop());
|
||||
return new ScopeDesc(this, pd);
|
||||
}
|
||||
|
||||
ScopeDesc* CompiledMethod::scope_desc_near(address pc) {
|
||||
PcDesc* pd = pc_desc_near(pc);
|
||||
guarantee(pd != NULL, "scope must be present");
|
||||
return new ScopeDesc(this, pd->scope_decode_offset(),
|
||||
pd->obj_decode_offset(), pd->should_reexecute(), pd->rethrow_exception(),
|
||||
pd->return_oop());
|
||||
return new ScopeDesc(this, pd);
|
||||
}
|
||||
|
||||
address CompiledMethod::oops_reloc_begin() const {
|
||||
|
@ -288,6 +288,8 @@ void DebugInformationRecorder::describe_scope(int pc_offset,
|
||||
bool rethrow_exception,
|
||||
bool is_method_handle_invoke,
|
||||
bool return_oop,
|
||||
bool has_ea_local_in_scope,
|
||||
bool arg_escape,
|
||||
DebugToken* locals,
|
||||
DebugToken* expressions,
|
||||
DebugToken* monitors) {
|
||||
@ -304,6 +306,8 @@ void DebugInformationRecorder::describe_scope(int pc_offset,
|
||||
last_pd->set_rethrow_exception(rethrow_exception);
|
||||
last_pd->set_is_method_handle_invoke(is_method_handle_invoke);
|
||||
last_pd->set_return_oop(return_oop);
|
||||
last_pd->set_has_ea_local_in_scope(has_ea_local_in_scope);
|
||||
last_pd->set_arg_escape(arg_escape);
|
||||
|
||||
// serialize sender stream offest
|
||||
stream()->write_int(sender_stream_offset);
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 1998, 2019, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 1998, 2020, 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
|
||||
@ -105,6 +105,8 @@ class DebugInformationRecorder: public ResourceObj {
|
||||
bool rethrow_exception = false,
|
||||
bool is_method_handle_invoke = false,
|
||||
bool return_oop = false,
|
||||
bool has_ea_local_in_scope = false,
|
||||
bool arg_escape = false,
|
||||
DebugToken* locals = NULL,
|
||||
DebugToken* expressions = NULL,
|
||||
DebugToken* monitors = NULL);
|
||||
|
@ -2419,9 +2419,7 @@ void nmethod::verify_interrupt_point(address call_site) {
|
||||
|
||||
PcDesc* pd = pc_desc_at(nativeCall_at(call_site)->return_address());
|
||||
assert(pd != NULL, "PcDesc must exist");
|
||||
for (ScopeDesc* sd = new ScopeDesc(this, pd->scope_decode_offset(),
|
||||
pd->obj_decode_offset(), pd->should_reexecute(), pd->rethrow_exception(),
|
||||
pd->return_oop());
|
||||
for (ScopeDesc* sd = new ScopeDesc(this, pd);
|
||||
!sd->is_top(); sd = sd->sender()) {
|
||||
sd->verify();
|
||||
}
|
||||
@ -3056,9 +3054,7 @@ const char* nmethod::reloc_string_for(u_char* begin, u_char* end) {
|
||||
ScopeDesc* nmethod::scope_desc_in(address begin, address end) {
|
||||
PcDesc* p = pc_desc_near(begin+1);
|
||||
if (p != NULL && p->real_pc(this) <= end) {
|
||||
return new ScopeDesc(this, p->scope_decode_offset(),
|
||||
p->obj_decode_offset(), p->should_reexecute(), p->rethrow_exception(),
|
||||
p->return_oop());
|
||||
return new ScopeDesc(this, p);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 1997, 2019, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 1997, 2020, 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
|
||||
@ -42,7 +42,9 @@ class PcDesc {
|
||||
PCDESC_reexecute = 1 << 0,
|
||||
PCDESC_is_method_handle_invoke = 1 << 1,
|
||||
PCDESC_return_oop = 1 << 2,
|
||||
PCDESC_rethrow_exception = 1 << 3
|
||||
PCDESC_rethrow_exception = 1 << 3,
|
||||
PCDESC_has_ea_local_in_scope = 1 << 4,
|
||||
PCDESC_arg_escape = 1 << 5
|
||||
};
|
||||
|
||||
int _flags;
|
||||
@ -89,6 +91,16 @@ class PcDesc {
|
||||
bool return_oop() const { return (_flags & PCDESC_return_oop) != 0; }
|
||||
void set_return_oop(bool z) { set_flag(PCDESC_return_oop, z); }
|
||||
|
||||
// Indicates if there are objects in scope that, based on escape analysis, are local to the
|
||||
// compiled method or local to the current thread, i.e. NoEscape or ArgEscape
|
||||
bool has_ea_local_in_scope() const { return (_flags & PCDESC_has_ea_local_in_scope) != 0; }
|
||||
void set_has_ea_local_in_scope(bool z) { set_flag(PCDESC_has_ea_local_in_scope, z); }
|
||||
|
||||
// Indicates if this pc descriptor is at a call site where objects that do not escape the
|
||||
// current thread are passed as arguments.
|
||||
bool arg_escape() const { return (_flags & PCDESC_arg_escape) != 0; }
|
||||
void set_arg_escape(bool z) { set_flag(PCDESC_arg_escape, z); }
|
||||
|
||||
// Returns the real pc
|
||||
address real_pc(const CompiledMethod* code) const;
|
||||
|
||||
|
@ -31,23 +31,16 @@
|
||||
#include "oops/oop.inline.hpp"
|
||||
#include "runtime/handles.inline.hpp"
|
||||
|
||||
ScopeDesc::ScopeDesc(const CompiledMethod* code, int decode_offset, int obj_decode_offset, bool reexecute, bool rethrow_exception, bool return_oop) {
|
||||
ScopeDesc::ScopeDesc(const CompiledMethod* code, PcDesc* pd, bool ignore_objects) {
|
||||
int obj_decode_offset = ignore_objects ? DebugInformationRecorder::serialized_null : pd->obj_decode_offset();
|
||||
_code = code;
|
||||
_decode_offset = decode_offset;
|
||||
_decode_offset = pd->scope_decode_offset();
|
||||
_objects = decode_object_values(obj_decode_offset);
|
||||
_reexecute = reexecute;
|
||||
_rethrow_exception = rethrow_exception;
|
||||
_return_oop = return_oop;
|
||||
decode_body();
|
||||
}
|
||||
|
||||
ScopeDesc::ScopeDesc(const CompiledMethod* code, int decode_offset, bool reexecute, bool rethrow_exception, bool return_oop) {
|
||||
_code = code;
|
||||
_decode_offset = decode_offset;
|
||||
_objects = decode_object_values(DebugInformationRecorder::serialized_null);
|
||||
_reexecute = reexecute;
|
||||
_rethrow_exception = rethrow_exception;
|
||||
_return_oop = return_oop;
|
||||
_reexecute = pd->should_reexecute();
|
||||
_rethrow_exception = pd->rethrow_exception();
|
||||
_return_oop = pd->return_oop();
|
||||
_has_ea_local_in_scope = ignore_objects ? false : pd->has_ea_local_in_scope();
|
||||
_arg_escape = ignore_objects ? false : pd->arg_escape();
|
||||
decode_body();
|
||||
}
|
||||
|
||||
@ -59,6 +52,8 @@ void ScopeDesc::initialize(const ScopeDesc* parent, int decode_offset) {
|
||||
_reexecute = false; //reexecute only applies to the first scope
|
||||
_rethrow_exception = false;
|
||||
_return_oop = false;
|
||||
_has_ea_local_in_scope = parent->has_ea_local_in_scope();
|
||||
_arg_escape = false;
|
||||
decode_body();
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 1997, 2019, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 1997, 2020, 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
|
||||
@ -60,12 +60,7 @@ class SimpleScopeDesc : public StackObj {
|
||||
class ScopeDesc : public ResourceObj {
|
||||
public:
|
||||
// Constructor
|
||||
ScopeDesc(const CompiledMethod* code, int decode_offset, int obj_decode_offset, bool reexecute, bool rethrow_exception, bool return_oop);
|
||||
|
||||
// Calls above, giving default value of "serialized_null" to the
|
||||
// "obj_decode_offset" argument. (We don't use a default argument to
|
||||
// avoid a .hpp-.hpp dependency.)
|
||||
ScopeDesc(const CompiledMethod* code, int decode_offset, bool reexecute, bool rethrow_exception, bool return_oop);
|
||||
ScopeDesc(const CompiledMethod* code, PcDesc* pd, bool ignore_objects = false);
|
||||
|
||||
// Direct access to scope
|
||||
ScopeDesc* at_offset(int decode_offset) { return new ScopeDesc(this, decode_offset); }
|
||||
@ -76,6 +71,10 @@ class ScopeDesc : public ResourceObj {
|
||||
bool should_reexecute() const { return _reexecute; }
|
||||
bool rethrow_exception() const { return _rethrow_exception; }
|
||||
bool return_oop() const { return _return_oop; }
|
||||
// Returns true if one or more NoEscape or ArgEscape objects exist in
|
||||
// any of the scopes at compiled pc.
|
||||
bool has_ea_local_in_scope() const { return _has_ea_local_in_scope; }
|
||||
bool arg_escape() const { return _arg_escape; }
|
||||
|
||||
GrowableArray<ScopeValue*>* locals();
|
||||
GrowableArray<ScopeValue*>* expressions();
|
||||
@ -105,6 +104,9 @@ class ScopeDesc : public ResourceObj {
|
||||
bool _reexecute;
|
||||
bool _rethrow_exception;
|
||||
bool _return_oop;
|
||||
bool _has_ea_local_in_scope; // One or more NoEscape or ArgEscape objects exist in
|
||||
// any of the scopes at compiled pc.
|
||||
bool _arg_escape; // Compiled Java call in youngest scope passes ArgEscape
|
||||
|
||||
// Decoding offsets
|
||||
int _decode_offset;
|
||||
|
@ -50,6 +50,7 @@
|
||||
#include "prims/whitebox.hpp"
|
||||
#include "runtime/arguments.hpp"
|
||||
#include "runtime/atomic.hpp"
|
||||
#include "runtime/escapeBarrier.hpp"
|
||||
#include "runtime/globals_extension.hpp"
|
||||
#include "runtime/handles.inline.hpp"
|
||||
#include "runtime/init.hpp"
|
||||
@ -798,19 +799,98 @@ Handle CompileBroker::create_thread_oop(const char* name, TRAPS) {
|
||||
CHECK_NH);
|
||||
}
|
||||
|
||||
#if defined(ASSERT) && COMPILER2_OR_JVMCI
|
||||
// Stress testing. Dedicated threads revert optimizations based on escape analysis concurrently to
|
||||
// the running java application. Configured with vm options DeoptimizeObjectsALot*.
|
||||
class DeoptimizeObjectsALotThread : public JavaThread {
|
||||
|
||||
JavaThread* CompileBroker::make_thread(jobject thread_handle, CompileQueue* queue, AbstractCompiler* comp, Thread* THREAD) {
|
||||
static void deopt_objs_alot_thread_entry(JavaThread* thread, TRAPS);
|
||||
void deoptimize_objects_alot_loop_single();
|
||||
void deoptimize_objects_alot_loop_all();
|
||||
|
||||
public:
|
||||
DeoptimizeObjectsALotThread() : JavaThread(&deopt_objs_alot_thread_entry) { }
|
||||
|
||||
bool is_hidden_from_external_view() const { return true; }
|
||||
};
|
||||
|
||||
// Entry for DeoptimizeObjectsALotThread. The threads are started in
|
||||
// CompileBroker::init_compiler_sweeper_threads() iff DeoptimizeObjectsALot is enabled
|
||||
void DeoptimizeObjectsALotThread::deopt_objs_alot_thread_entry(JavaThread* thread, TRAPS) {
|
||||
DeoptimizeObjectsALotThread* dt = ((DeoptimizeObjectsALotThread*) thread);
|
||||
bool enter_single_loop;
|
||||
{
|
||||
MonitorLocker ml(dt, EscapeBarrier_lock, Mutex::_no_safepoint_check_flag);
|
||||
static int single_thread_count = 0;
|
||||
enter_single_loop = single_thread_count++ < DeoptimizeObjectsALotThreadCountSingle;
|
||||
}
|
||||
if (enter_single_loop) {
|
||||
dt->deoptimize_objects_alot_loop_single();
|
||||
} else {
|
||||
dt->deoptimize_objects_alot_loop_all();
|
||||
}
|
||||
}
|
||||
|
||||
// Execute EscapeBarriers in an endless loop to revert optimizations based on escape analysis. Each
|
||||
// barrier targets a single thread which is selected round robin.
|
||||
void DeoptimizeObjectsALotThread::deoptimize_objects_alot_loop_single() {
|
||||
HandleMark hm(this);
|
||||
while (true) {
|
||||
for (JavaThreadIteratorWithHandle jtiwh; JavaThread *deoptee_thread = jtiwh.next(); ) {
|
||||
{ // Begin new scope for escape barrier
|
||||
HandleMarkCleaner hmc(this);
|
||||
ResourceMark rm(this);
|
||||
EscapeBarrier eb(true, this, deoptee_thread);
|
||||
eb.deoptimize_objects(100);
|
||||
}
|
||||
// Now sleep after the escape barriers destructor resumed deoptee_thread.
|
||||
sleep(DeoptimizeObjectsALotInterval);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Execute EscapeBarriers in an endless loop to revert optimizations based on escape analysis. Each
|
||||
// barrier targets all java threads in the vm at once.
|
||||
void DeoptimizeObjectsALotThread::deoptimize_objects_alot_loop_all() {
|
||||
HandleMark hm(this);
|
||||
while (true) {
|
||||
{ // Begin new scope for escape barrier
|
||||
HandleMarkCleaner hmc(this);
|
||||
ResourceMark rm(this);
|
||||
EscapeBarrier eb(true, this);
|
||||
eb.deoptimize_objects_all_threads();
|
||||
}
|
||||
// Now sleep after the escape barriers destructor resumed the java threads.
|
||||
sleep(DeoptimizeObjectsALotInterval);
|
||||
}
|
||||
}
|
||||
#endif // defined(ASSERT) && COMPILER2_OR_JVMCI
|
||||
|
||||
|
||||
JavaThread* CompileBroker::make_thread(ThreadType type, jobject thread_handle, CompileQueue* queue, AbstractCompiler* comp, Thread* THREAD) {
|
||||
JavaThread* new_thread = NULL;
|
||||
{
|
||||
MutexLocker mu(THREAD, Threads_lock);
|
||||
if (comp != NULL) {
|
||||
if (!InjectCompilerCreationFailure || comp->num_compiler_threads() == 0) {
|
||||
CompilerCounters* counters = new CompilerCounters();
|
||||
new_thread = new CompilerThread(queue, counters);
|
||||
}
|
||||
} else {
|
||||
new_thread = new CodeCacheSweeperThread();
|
||||
switch (type) {
|
||||
case compiler_t:
|
||||
assert(comp != NULL, "Compiler instance missing.");
|
||||
if (!InjectCompilerCreationFailure || comp->num_compiler_threads() == 0) {
|
||||
CompilerCounters* counters = new CompilerCounters();
|
||||
new_thread = new CompilerThread(queue, counters);
|
||||
}
|
||||
break;
|
||||
case sweeper_t:
|
||||
new_thread = new CodeCacheSweeperThread();
|
||||
break;
|
||||
#if defined(ASSERT) && COMPILER2_OR_JVMCI
|
||||
case deoptimizer_t:
|
||||
new_thread = new DeoptimizeObjectsALotThread();
|
||||
break;
|
||||
#endif // ASSERT
|
||||
default:
|
||||
ShouldNotReachHere();
|
||||
}
|
||||
|
||||
// At this point the new CompilerThread data-races with this startup
|
||||
// thread (which I believe is the primoridal thread and NOT the VM
|
||||
// thread). This means Java bytecodes being executed at startup can
|
||||
@ -852,7 +932,7 @@ JavaThread* CompileBroker::make_thread(jobject thread_handle, CompileQueue* queu
|
||||
java_lang_Thread::set_daemon(JNIHandles::resolve_non_null(thread_handle));
|
||||
|
||||
new_thread->set_threadObj(JNIHandles::resolve_non_null(thread_handle));
|
||||
if (comp != NULL) {
|
||||
if (type == compiler_t) {
|
||||
new_thread->as_CompilerThread()->set_compiler(comp);
|
||||
}
|
||||
Threads::add(new_thread);
|
||||
@ -862,7 +942,7 @@ JavaThread* CompileBroker::make_thread(jobject thread_handle, CompileQueue* queu
|
||||
|
||||
// First release lock before aborting VM.
|
||||
if (new_thread == NULL || new_thread->osthread() == NULL) {
|
||||
if (UseDynamicNumberOfCompilerThreads && comp != NULL && comp->num_compiler_threads() > 0) {
|
||||
if (UseDynamicNumberOfCompilerThreads && type == compiler_t && comp->num_compiler_threads() > 0) {
|
||||
if (new_thread != NULL) {
|
||||
new_thread->smr_delete();
|
||||
}
|
||||
@ -917,7 +997,7 @@ void CompileBroker::init_compiler_sweeper_threads() {
|
||||
_compiler2_logs[i] = NULL;
|
||||
|
||||
if (!UseDynamicNumberOfCompilerThreads || i == 0) {
|
||||
JavaThread *ct = make_thread(thread_handle, _c2_compile_queue, _compilers[1], THREAD);
|
||||
JavaThread *ct = make_thread(compiler_t, thread_handle, _c2_compile_queue, _compilers[1], THREAD);
|
||||
assert(ct != NULL, "should have been handled for initial thread");
|
||||
_compilers[1]->set_num_compiler_threads(i + 1);
|
||||
if (TraceCompilerThreads) {
|
||||
@ -937,7 +1017,7 @@ void CompileBroker::init_compiler_sweeper_threads() {
|
||||
_compiler1_logs[i] = NULL;
|
||||
|
||||
if (!UseDynamicNumberOfCompilerThreads || i == 0) {
|
||||
JavaThread *ct = make_thread(thread_handle, _c1_compile_queue, _compilers[0], THREAD);
|
||||
JavaThread *ct = make_thread(compiler_t, thread_handle, _c1_compile_queue, _compilers[0], THREAD);
|
||||
assert(ct != NULL, "should have been handled for initial thread");
|
||||
_compilers[0]->set_num_compiler_threads(i + 1);
|
||||
if (TraceCompilerThreads) {
|
||||
@ -956,8 +1036,20 @@ void CompileBroker::init_compiler_sweeper_threads() {
|
||||
// Initialize the sweeper thread
|
||||
Handle thread_oop = create_thread_oop("Sweeper thread", CHECK);
|
||||
jobject thread_handle = JNIHandles::make_local(THREAD, thread_oop());
|
||||
make_thread(thread_handle, NULL, NULL, THREAD);
|
||||
make_thread(sweeper_t, thread_handle, NULL, NULL, THREAD);
|
||||
}
|
||||
|
||||
#if defined(ASSERT) && COMPILER2_OR_JVMCI
|
||||
if (DeoptimizeObjectsALot) {
|
||||
// Initialize and start the object deoptimizer threads
|
||||
const int total_count = DeoptimizeObjectsALotThreadCountSingle + DeoptimizeObjectsALotThreadCountAll;
|
||||
for (int count = 0; count < total_count; count++) {
|
||||
Handle thread_oop = create_thread_oop("Deoptimize objects a lot single mode", CHECK);
|
||||
jobject thread_handle = JNIHandles::make_local(THREAD, thread_oop());
|
||||
make_thread(deoptimizer_t, thread_handle, NULL, NULL, THREAD);
|
||||
}
|
||||
}
|
||||
#endif // defined(ASSERT) && COMPILER2_OR_JVMCI
|
||||
}
|
||||
|
||||
void CompileBroker::possibly_add_compiler_threads(Thread* THREAD) {
|
||||
@ -1011,7 +1103,7 @@ void CompileBroker::possibly_add_compiler_threads(Thread* THREAD) {
|
||||
_compiler2_objects[i] = thread_handle;
|
||||
}
|
||||
#endif
|
||||
JavaThread *ct = make_thread(compiler2_object(i), _c2_compile_queue, _compilers[1], THREAD);
|
||||
JavaThread *ct = make_thread(compiler_t, compiler2_object(i), _c2_compile_queue, _compilers[1], THREAD);
|
||||
if (ct == NULL) break;
|
||||
_compilers[1]->set_num_compiler_threads(i + 1);
|
||||
if (TraceCompilerThreads) {
|
||||
@ -1031,7 +1123,7 @@ void CompileBroker::possibly_add_compiler_threads(Thread* THREAD) {
|
||||
(int)(available_cc_p / (128*K)));
|
||||
|
||||
for (int i = old_c1_count; i < new_c1_count; i++) {
|
||||
JavaThread *ct = make_thread(compiler1_object(i), _c1_compile_queue, _compilers[0], THREAD);
|
||||
JavaThread *ct = make_thread(compiler_t, compiler1_object(i), _c1_compile_queue, _compilers[0], THREAD);
|
||||
if (ct == NULL) break;
|
||||
_compilers[0]->set_num_compiler_threads(i + 1);
|
||||
if (TraceCompilerThreads) {
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 1999, 2019, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 1999, 2020, 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
|
||||
@ -228,8 +228,14 @@ class CompileBroker: AllStatic {
|
||||
|
||||
static volatile int _print_compilation_warning;
|
||||
|
||||
enum ThreadType {
|
||||
compiler_t,
|
||||
sweeper_t,
|
||||
deoptimizer_t
|
||||
};
|
||||
|
||||
static Handle create_thread_oop(const char* name, TRAPS);
|
||||
static JavaThread* make_thread(jobject thread_oop, CompileQueue* queue, AbstractCompiler* comp, Thread* THREAD);
|
||||
static JavaThread* make_thread(ThreadType type, jobject thread_oop, CompileQueue* queue, AbstractCompiler* comp, Thread* THREAD);
|
||||
static void init_compiler_sweeper_threads();
|
||||
static void possibly_add_compiler_threads(Thread* THREAD);
|
||||
static bool compilation_is_prohibited(const methodHandle& method, int osr_bci, int comp_level, bool excluded);
|
||||
|
@ -33,6 +33,7 @@
|
||||
#include "jfr/leakprofiler/checkpoint/rootResolver.hpp"
|
||||
#include "jfr/utilities/jfrThreadIterator.hpp"
|
||||
#include "memory/iterator.hpp"
|
||||
#include "prims/jvmtiDeferredUpdates.hpp"
|
||||
#include "oops/klass.hpp"
|
||||
#include "oops/oop.hpp"
|
||||
#include "prims/jvmtiThreadState.hpp"
|
||||
@ -279,7 +280,7 @@ bool ReferenceToThreadRootClosure::do_thread_stack_detailed(JavaThread* jt) {
|
||||
return true;
|
||||
}
|
||||
|
||||
GrowableArray<jvmtiDeferredLocalVariableSet*>* const list = jt->deferred_locals();
|
||||
GrowableArray<jvmtiDeferredLocalVariableSet*>* const list = JvmtiDeferredUpdates::deferred_locals(jt);
|
||||
if (list != NULL) {
|
||||
for (int i = 0; i < list->length(); i++) {
|
||||
list->at(i)->oops_do(&rcl);
|
||||
|
@ -1188,7 +1188,11 @@ void CodeInstaller::record_scope(jint pc_offset, JVMCIObject position, ScopeMode
|
||||
throw_exception = jvmci_env()->get_BytecodeFrame_rethrowException(frame) == JNI_TRUE;
|
||||
}
|
||||
|
||||
// has_ea_local_in_scope and arg_escape should be added to JVMCI
|
||||
const bool has_ea_local_in_scope = false;
|
||||
const bool arg_escape = false;
|
||||
_debug_recorder->describe_scope(pc_offset, method, NULL, bci, reexecute, throw_exception, is_mh_invoke, return_oop,
|
||||
has_ea_local_in_scope, arg_escape,
|
||||
locals_token, expressions_token, monitors_token);
|
||||
}
|
||||
|
||||
|
@ -93,8 +93,7 @@ void C2Compiler::compile_method(ciEnv* env, ciMethod* target, int entry_bci, boo
|
||||
assert(is_initialized(), "Compiler thread must be initialized");
|
||||
|
||||
bool subsume_loads = SubsumeLoads;
|
||||
bool do_escape_analysis = DoEscapeAnalysis && !env->should_retain_local_variables()
|
||||
&& !env->jvmti_can_get_owned_monitor_info();
|
||||
bool do_escape_analysis = DoEscapeAnalysis;
|
||||
bool eliminate_boxing = EliminateAutoBox;
|
||||
|
||||
while (!env->failing()) {
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 1997, 2019, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 1997, 2020, 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
|
||||
@ -329,7 +329,8 @@ public:
|
||||
const TypePtr* adr_type = NULL)
|
||||
: MultiNode( edges ),
|
||||
_jvms(jvms),
|
||||
_adr_type(adr_type)
|
||||
_adr_type(adr_type),
|
||||
_has_ea_local_in_scope(false)
|
||||
{
|
||||
init_class_id(Class_SafePoint);
|
||||
}
|
||||
@ -337,6 +338,7 @@ public:
|
||||
JVMState* const _jvms; // Pointer to list of JVM State objects
|
||||
const TypePtr* _adr_type; // What type of memory does this node produce?
|
||||
ReplacedNodes _replaced_nodes; // During parsing: list of pair of nodes from calls to GraphKit::replace_in_map()
|
||||
bool _has_ea_local_in_scope; // NoEscape or ArgEscape objects in JVM States
|
||||
|
||||
// Many calls take *all* of memory as input,
|
||||
// but some produce a limited subset of that memory as output.
|
||||
@ -456,6 +458,12 @@ public:
|
||||
bool has_replaced_nodes() const {
|
||||
return !_replaced_nodes.is_empty();
|
||||
}
|
||||
void set_has_ea_local_in_scope(bool b) {
|
||||
_has_ea_local_in_scope = b;
|
||||
}
|
||||
bool has_ea_local_in_scope() const {
|
||||
return _has_ea_local_in_scope;
|
||||
}
|
||||
|
||||
void disconnect_from_root(PhaseIterGVN *igvn);
|
||||
|
||||
@ -658,6 +666,7 @@ protected:
|
||||
bool _method_handle_invoke;
|
||||
bool _override_symbolic_info; // Override symbolic call site info from bytecode
|
||||
ciMethod* _method; // Method being direct called
|
||||
bool _arg_escape; // ArgEscape in parameter list
|
||||
public:
|
||||
const int _bci; // Byte Code Index of call byte code
|
||||
CallJavaNode(const TypeFunc* tf , address addr, ciMethod* method, int bci)
|
||||
@ -665,7 +674,8 @@ public:
|
||||
_optimized_virtual(false),
|
||||
_method_handle_invoke(false),
|
||||
_override_symbolic_info(false),
|
||||
_method(method), _bci(bci)
|
||||
_method(method),
|
||||
_arg_escape(false), _bci(bci)
|
||||
{
|
||||
init_class_id(Class_CallJava);
|
||||
}
|
||||
@ -679,6 +689,8 @@ public:
|
||||
bool is_method_handle_invoke() const { return _method_handle_invoke; }
|
||||
void set_override_symbolic_info(bool f) { _override_symbolic_info = f; }
|
||||
bool override_symbolic_info() const { return _override_symbolic_info; }
|
||||
void set_arg_escape(bool f) { _arg_escape = f; }
|
||||
bool arg_escape() const { return _arg_escape; }
|
||||
void copy_call_debug_info(PhaseIterGVN* phase, SafePointNode *sfpt);
|
||||
|
||||
DEBUG_ONLY( bool validate_symbolic_info() const; )
|
||||
|
@ -123,6 +123,7 @@ bool ConnectionGraph::compute_escape() {
|
||||
GrowableArray<JavaObjectNode*> java_objects_worklist;
|
||||
GrowableArray<JavaObjectNode*> non_escaped_worklist;
|
||||
GrowableArray<FieldNode*> oop_fields_worklist;
|
||||
GrowableArray<SafePointNode*> sfn_worklist;
|
||||
DEBUG_ONLY( GrowableArray<Node*> addp_worklist; )
|
||||
|
||||
{ Compile::TracePhase tp("connectionGraph", &Phase::timers[Phase::_t_connectionGraph]);
|
||||
@ -188,6 +189,9 @@ bool ConnectionGraph::compute_escape() {
|
||||
Node* m = n->fast_out(i); // Get user
|
||||
ideal_nodes.push(m);
|
||||
}
|
||||
if (n-> is_SafePoint()) {
|
||||
sfn_worklist.append(n->as_SafePoint());
|
||||
}
|
||||
}
|
||||
if (non_escaped_worklist.length() == 0) {
|
||||
_collecting = false;
|
||||
@ -317,9 +321,89 @@ bool ConnectionGraph::compute_escape() {
|
||||
tty->cr();
|
||||
#endif
|
||||
}
|
||||
|
||||
// Annotate at safepoints if they have <= ArgEscape objects in their scope and at
|
||||
// java calls if they pass ArgEscape objects as parameters.
|
||||
if (has_non_escaping_obj &&
|
||||
(C->env()->should_retain_local_variables() ||
|
||||
C->env()->jvmti_can_get_owned_monitor_info() ||
|
||||
C->env()->jvmti_can_walk_any_space() ||
|
||||
DeoptimizeObjectsALot)) {
|
||||
int sfn_length = sfn_worklist.length();
|
||||
for (int next = 0; next < sfn_length; next++) {
|
||||
SafePointNode* sfn = sfn_worklist.at(next);
|
||||
sfn->set_has_ea_local_in_scope(has_ea_local_in_scope(sfn));
|
||||
if (sfn->is_CallJava()) {
|
||||
CallJavaNode* call = sfn->as_CallJava();
|
||||
call->set_arg_escape(has_arg_escape(call));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return has_non_escaping_obj;
|
||||
}
|
||||
|
||||
// Returns true if there is an object in the scope of sfn that does not escape globally.
|
||||
bool ConnectionGraph::has_ea_local_in_scope(SafePointNode* sfn) {
|
||||
Compile* C = _compile;
|
||||
for (JVMState* jvms = sfn->jvms(); jvms != NULL; jvms = jvms->caller()) {
|
||||
if (C->env()->should_retain_local_variables() || C->env()->jvmti_can_walk_any_space() ||
|
||||
DeoptimizeObjectsALot) {
|
||||
// Jvmti agents can access locals. Must provide info about local objects at runtime.
|
||||
int num_locs = jvms->loc_size();
|
||||
for (int idx = 0; idx < num_locs; idx++) {
|
||||
Node* l = sfn->local(jvms, idx);
|
||||
if (not_global_escape(l)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (C->env()->jvmti_can_get_owned_monitor_info() ||
|
||||
C->env()->jvmti_can_walk_any_space() || DeoptimizeObjectsALot) {
|
||||
// Jvmti agents can read monitors. Must provide info about locked objects at runtime.
|
||||
int num_mon = jvms->nof_monitors();
|
||||
for (int idx = 0; idx < num_mon; idx++) {
|
||||
Node* m = sfn->monitor_obj(jvms, idx);
|
||||
if (m != NULL && not_global_escape(m)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Returns true if at least one of the arguments to the call is an object
|
||||
// that does not escape globally.
|
||||
bool ConnectionGraph::has_arg_escape(CallJavaNode* call) {
|
||||
if (call->method() != NULL) {
|
||||
uint max_idx = TypeFunc::Parms + call->method()->arg_size();
|
||||
for (uint idx = TypeFunc::Parms; idx < max_idx; idx++) {
|
||||
Node* p = call->in(idx);
|
||||
if (not_global_escape(p)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const char* name = call->as_CallStaticJava()->_name;
|
||||
assert(name != NULL, "no name");
|
||||
// no arg escapes through uncommon traps
|
||||
if (strcmp(name, "uncommon_trap") != 0) {
|
||||
// process_call_arguments() assumes that all arguments escape globally
|
||||
const TypeTuple* d = call->tf()->domain();
|
||||
for (uint i = TypeFunc::Parms; i < d->cnt(); i++) {
|
||||
const Type* at = d->field_at(i);
|
||||
if (at->isa_oopptr() != NULL) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Utility function for nodes that load an object
|
||||
void ConnectionGraph::add_objload_to_connection_graph(Node *n, Unique_Node_List *delayed_worklist) {
|
||||
// Using isa_ptr() instead of isa_oopptr() for LoadP and Phi because
|
||||
|
@ -553,6 +553,11 @@ private:
|
||||
return (phi == NULL) ? NULL : phi->as_Phi();
|
||||
}
|
||||
|
||||
// Returns true if there is an object in the scope of sfn that does not escape globally.
|
||||
bool has_ea_local_in_scope(SafePointNode* sfn);
|
||||
|
||||
bool has_arg_escape(CallJavaNode* call);
|
||||
|
||||
// Notify optimizer that a node has been modified
|
||||
void record_for_optimizer(Node *n);
|
||||
|
||||
|
@ -820,10 +820,11 @@ public:
|
||||
OopMap* _oop_map; // Array of OopMap info (8-bit char) for GC
|
||||
JVMState* _jvms; // Pointer to list of JVM State Objects
|
||||
uint _jvmadj; // Extra delta to jvms indexes (mach. args)
|
||||
bool _has_ea_local_in_scope; // NoEscape or ArgEscape objects in JVM States
|
||||
OopMap* oop_map() const { return _oop_map; }
|
||||
void set_oop_map(OopMap* om) { _oop_map = om; }
|
||||
|
||||
MachSafePointNode() : MachReturnNode(), _oop_map(NULL), _jvms(NULL), _jvmadj(0) {
|
||||
MachSafePointNode() : MachReturnNode(), _oop_map(NULL), _jvms(NULL), _jvmadj(0), _has_ea_local_in_scope(false) {
|
||||
init_class_id(Class_MachSafePoint);
|
||||
}
|
||||
|
||||
@ -922,6 +923,7 @@ public:
|
||||
int _bci; // Byte Code index of call byte code
|
||||
bool _optimized_virtual; // Tells if node is a static call or an optimized virtual
|
||||
bool _method_handle_invoke; // Tells if the call has to preserve SP
|
||||
bool _arg_escape; // ArgEscape in parameter list
|
||||
MachCallJavaNode() : MachCallNode(), _override_symbolic_info(false) {
|
||||
init_class_id(Class_MachCallJava);
|
||||
}
|
||||
|
@ -1051,11 +1051,12 @@ void PhaseMacroExpand::process_users_of_allocation(CallNode *alloc) {
|
||||
}
|
||||
|
||||
bool PhaseMacroExpand::eliminate_allocate_node(AllocateNode *alloc) {
|
||||
// Don't do scalar replacement if the frame can be popped by JVMTI:
|
||||
// if reallocation fails during deoptimization we'll pop all
|
||||
// If reallocation fails during deoptimization we'll pop all
|
||||
// interpreter frames for this compiled frame and that won't play
|
||||
// nice with JVMTI popframe.
|
||||
if (!EliminateAllocations || JvmtiExport::can_pop_frame() || !alloc->_is_non_escaping) {
|
||||
// We avoid this issue by eager reallocation when the popframe request
|
||||
// is received.
|
||||
if (!EliminateAllocations || !alloc->_is_non_escaping) {
|
||||
return false;
|
||||
}
|
||||
Node* klass = alloc->in(AllocateNode::KlassNode);
|
||||
|
@ -1251,6 +1251,7 @@ MachNode *Matcher::match_sfpt( SafePointNode *sfpt ) {
|
||||
is_method_handle_invoke = call_java->is_method_handle_invoke();
|
||||
mcall_java->_method_handle_invoke = is_method_handle_invoke;
|
||||
mcall_java->_override_symbolic_info = call_java->override_symbolic_info();
|
||||
mcall_java->_arg_escape = call_java->arg_escape();
|
||||
if (is_method_handle_invoke) {
|
||||
C->set_has_method_handle_invokes(true);
|
||||
}
|
||||
@ -1275,6 +1276,7 @@ MachNode *Matcher::match_sfpt( SafePointNode *sfpt ) {
|
||||
msfpt = mn->as_MachSafePoint();
|
||||
cnt = TypeFunc::Parms;
|
||||
}
|
||||
msfpt->_has_ea_local_in_scope = sfpt->has_ea_local_in_scope();
|
||||
|
||||
// Advertise the correct memory effects (for anti-dependence computation).
|
||||
msfpt->set_adr_type(sfpt->adr_type());
|
||||
|
@ -1003,6 +1003,8 @@ void PhaseOutput::Process_OopMap_Node(MachNode *mach, int current_offset) {
|
||||
int safepoint_pc_offset = current_offset;
|
||||
bool is_method_handle_invoke = false;
|
||||
bool return_oop = false;
|
||||
bool has_ea_local_in_scope = sfn->_has_ea_local_in_scope;
|
||||
bool arg_escape = false;
|
||||
|
||||
// Add the safepoint in the DebugInfoRecorder
|
||||
if( !mach->is_MachCall() ) {
|
||||
@ -1017,6 +1019,7 @@ void PhaseOutput::Process_OopMap_Node(MachNode *mach, int current_offset) {
|
||||
assert(C->has_method_handle_invokes(), "must have been set during call generation");
|
||||
is_method_handle_invoke = true;
|
||||
}
|
||||
arg_escape = mcall->as_MachCallJava()->_arg_escape;
|
||||
}
|
||||
|
||||
// Check if a call returns an object.
|
||||
@ -1137,7 +1140,10 @@ void PhaseOutput::Process_OopMap_Node(MachNode *mach, int current_offset) {
|
||||
// Now we can describe the scope.
|
||||
methodHandle null_mh;
|
||||
bool rethrow_exception = false;
|
||||
C->debug_info()->describe_scope(safepoint_pc_offset, null_mh, scope_method, jvms->bci(), jvms->should_reexecute(), rethrow_exception, is_method_handle_invoke, return_oop, locvals, expvals, monvals);
|
||||
C->debug_info()->describe_scope(safepoint_pc_offset, null_mh, scope_method, jvms->bci(),
|
||||
jvms->should_reexecute(), rethrow_exception, is_method_handle_invoke,
|
||||
return_oop, has_ea_local_in_scope, arg_escape,
|
||||
locvals, expvals, monvals);
|
||||
} // End jvms loop
|
||||
|
||||
// Mark the end of the scope set.
|
||||
|
@ -275,7 +275,7 @@ void JvmtiCodeBlobEvents::build_jvmti_addr_location_map(nmethod *nm,
|
||||
|
||||
address scopes_data = nm->scopes_data_begin();
|
||||
for( pcd = nm->scopes_pcs_begin(); pcd < nm->scopes_pcs_end(); ++pcd ) {
|
||||
ScopeDesc sc0(nm, pcd->scope_decode_offset(), pcd->should_reexecute(), pcd->rethrow_exception(), pcd->return_oop());
|
||||
ScopeDesc sc0(nm, pcd, true);
|
||||
ScopeDesc *sd = &sc0;
|
||||
while( !sd->is_top() ) { sd = sd->sender(); }
|
||||
int bci = sd->bci();
|
||||
|
87
src/hotspot/share/prims/jvmtiDeferredUpdates.cpp
Normal file
87
src/hotspot/share/prims/jvmtiDeferredUpdates.cpp
Normal file
@ -0,0 +1,87 @@
|
||||
/*
|
||||
* Copyright (c) 1997, 2020, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2020 SAP SE. 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 "prims/jvmtiDeferredUpdates.hpp"
|
||||
|
||||
void JvmtiDeferredUpdates::create_for(JavaThread* thread) {
|
||||
assert(thread->deferred_updates() == NULL, "already allocated");
|
||||
thread->set_deferred_updates(new JvmtiDeferredUpdates());
|
||||
}
|
||||
|
||||
JvmtiDeferredUpdates::~JvmtiDeferredUpdates() {
|
||||
while (_deferred_locals_updates.length() != 0) {
|
||||
jvmtiDeferredLocalVariableSet* dlv = _deferred_locals_updates.pop();
|
||||
// individual jvmtiDeferredLocalVariableSet are CHeapObj's
|
||||
delete dlv;
|
||||
}
|
||||
}
|
||||
|
||||
void JvmtiDeferredUpdates::inc_relock_count_after_wait(JavaThread* thread) {
|
||||
if (thread->deferred_updates() == NULL) {
|
||||
create_for(thread);
|
||||
}
|
||||
thread->deferred_updates()->inc_relock_count_after_wait();
|
||||
}
|
||||
|
||||
int JvmtiDeferredUpdates::get_and_reset_relock_count_after_wait(JavaThread* jt) {
|
||||
JvmtiDeferredUpdates* updates = jt->deferred_updates();
|
||||
int result = 0;
|
||||
if (updates != NULL) {
|
||||
result = updates->get_and_reset_relock_count_after_wait();
|
||||
if (updates->count() == 0) {
|
||||
delete updates;
|
||||
jt->set_deferred_updates(NULL);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void JvmtiDeferredUpdates::delete_updates_for_frame(JavaThread* jt, intptr_t* frame_id) {
|
||||
JvmtiDeferredUpdates* updates = jt->deferred_updates();
|
||||
if (updates != NULL) {
|
||||
GrowableArray<jvmtiDeferredLocalVariableSet*>* list = updates->deferred_locals();
|
||||
assert(list->length() > 0, "Updates holder not deleted");
|
||||
int i = 0;
|
||||
do {
|
||||
// Because of inlining we could have multiple vframes for a single frame
|
||||
// and several of the vframes could have deferred writes. Find them all.
|
||||
jvmtiDeferredLocalVariableSet* dlv = list->at(i);
|
||||
if (dlv->id() == frame_id) {
|
||||
list->remove_at(i);
|
||||
// individual jvmtiDeferredLocalVariableSet are CHeapObj's
|
||||
delete dlv;
|
||||
} else {
|
||||
i++;
|
||||
}
|
||||
} while ( i < list->length() );
|
||||
if (updates->count() == 0) {
|
||||
jt->set_deferred_updates(NULL);
|
||||
// Free deferred updates.
|
||||
// Note, the 'list' of local variable updates is embedded in 'updates'.
|
||||
delete updates;
|
||||
}
|
||||
}
|
||||
}
|
159
src/hotspot/share/prims/jvmtiDeferredUpdates.hpp
Normal file
159
src/hotspot/share/prims/jvmtiDeferredUpdates.hpp
Normal file
@ -0,0 +1,159 @@
|
||||
/*
|
||||
* Copyright (c) 1997, 2020, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2020 SAP SE. 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_PRIMS_JVMTIDEFERREDUPDATES_HPP
|
||||
#define SHARE_PRIMS_JVMTIDEFERREDUPDATES_HPP
|
||||
|
||||
#include "runtime/thread.inline.hpp"
|
||||
#include "utilities/globalDefinitions.hpp"
|
||||
#include "utilities/growableArray.hpp"
|
||||
|
||||
class jvmtiDeferredLocalVariable : public CHeapObj<mtCompiler> {
|
||||
|
||||
private:
|
||||
|
||||
BasicType _type;
|
||||
jvalue _value;
|
||||
int _index;
|
||||
|
||||
public:
|
||||
|
||||
jvmtiDeferredLocalVariable(int index, BasicType type, jvalue value);
|
||||
|
||||
BasicType type(void) { return _type; }
|
||||
int index(void) { return _index; }
|
||||
jvalue value(void) { return _value; }
|
||||
|
||||
// Only mutator is for value as only it can change
|
||||
void set_value(jvalue value) { _value = value; }
|
||||
|
||||
// For gc
|
||||
oop* oop_addr(void) { return (oop*) &_value.l; }
|
||||
};
|
||||
|
||||
// In order to implement set_locals for compiled vframes we must
|
||||
// store updated locals in a data structure that contains enough
|
||||
// information to recognize equality with a vframe and to store
|
||||
// any updated locals.
|
||||
|
||||
class StackValueCollection;
|
||||
|
||||
class jvmtiDeferredLocalVariableSet : public CHeapObj<mtCompiler> {
|
||||
friend class compiledVFrame;
|
||||
|
||||
private:
|
||||
|
||||
Method* _method;
|
||||
int _bci;
|
||||
intptr_t* _id;
|
||||
int _vframe_id;
|
||||
GrowableArray<jvmtiDeferredLocalVariable*>* _locals;
|
||||
bool _objects_are_deoptimized;
|
||||
|
||||
void update_value(StackValueCollection* locals, BasicType type, int index, jvalue value);
|
||||
|
||||
void set_value_at(int idx, BasicType typ, jvalue val);
|
||||
|
||||
public:
|
||||
// JVM state
|
||||
Method* method() const { return _method; }
|
||||
int bci() const { return _bci; }
|
||||
intptr_t* id() const { return _id; }
|
||||
int vframe_id() const { return _vframe_id; }
|
||||
bool objects_are_deoptimized() const { return _objects_are_deoptimized; }
|
||||
|
||||
void update_locals(StackValueCollection* locals);
|
||||
void update_stack(StackValueCollection* locals);
|
||||
void update_monitors(GrowableArray<MonitorInfo*>* monitors);
|
||||
void set_objs_are_deoptimized() { _objects_are_deoptimized = true; }
|
||||
|
||||
// Does the vframe match this jvmtiDeferredLocalVariableSet
|
||||
bool matches(const vframe* vf);
|
||||
|
||||
// Does the underlying physical frame match this jvmtiDeferredLocalVariableSet
|
||||
bool matches(intptr_t* fr_id) { return id() == fr_id; }
|
||||
|
||||
// GC
|
||||
void oops_do(OopClosure* f);
|
||||
|
||||
// constructor
|
||||
jvmtiDeferredLocalVariableSet(Method* method, int bci, intptr_t* id, int vframe_id);
|
||||
|
||||
// destructor
|
||||
~jvmtiDeferredLocalVariableSet();
|
||||
};
|
||||
|
||||
// Holds updates for compiled frames by JVMTI agents that cannot be performed immediately.
|
||||
|
||||
class JvmtiDeferredUpdates : public CHeapObj<mtCompiler> {
|
||||
|
||||
// Relocking has to be deferred if the lock owning thread is currently waiting on the monitor.
|
||||
int _relock_count_after_wait;
|
||||
|
||||
// Deferred updates of locals, expressions, and monitors
|
||||
GrowableArray<jvmtiDeferredLocalVariableSet*> _deferred_locals_updates;
|
||||
|
||||
void inc_relock_count_after_wait() {
|
||||
_relock_count_after_wait++;
|
||||
}
|
||||
|
||||
int get_and_reset_relock_count_after_wait() {
|
||||
int result = _relock_count_after_wait;
|
||||
_relock_count_after_wait = 0;
|
||||
return result;
|
||||
}
|
||||
|
||||
GrowableArray<jvmtiDeferredLocalVariableSet*>* deferred_locals() { return &_deferred_locals_updates; }
|
||||
|
||||
JvmtiDeferredUpdates() :
|
||||
_relock_count_after_wait(0),
|
||||
_deferred_locals_updates((ResourceObj::set_allocation_type((address) &_deferred_locals_updates,
|
||||
ResourceObj::C_HEAP), 1), mtCompiler) { }
|
||||
|
||||
public:
|
||||
~JvmtiDeferredUpdates();
|
||||
|
||||
static void create_for(JavaThread* thread);
|
||||
|
||||
static GrowableArray<jvmtiDeferredLocalVariableSet*>* deferred_locals(JavaThread* jt) {
|
||||
return jt->deferred_updates() == NULL ? NULL : jt->deferred_updates()->deferred_locals();
|
||||
}
|
||||
|
||||
// Relocking has to be deferred if the lock owning thread is currently waiting on the monitor.
|
||||
static int get_and_reset_relock_count_after_wait(JavaThread* jt);
|
||||
static void inc_relock_count_after_wait(JavaThread* thread);
|
||||
|
||||
// Delete deferred updates for the compiled frame with id 'frame_id' on the
|
||||
// given thread's stack. The thread's JvmtiDeferredUpdates instance will be
|
||||
// deleted too if no updates remain.
|
||||
static void delete_updates_for_frame(JavaThread* jt, intptr_t* frame_id);
|
||||
|
||||
// Number of deferred updates
|
||||
int count() const {
|
||||
return _deferred_locals_updates.length() + (_relock_count_after_wait > 0 ? 1 : 0);
|
||||
}
|
||||
};
|
||||
|
||||
#endif // SHARE_PRIMS_JVMTIDEFERREDUPDATES_HPP
|
@ -1206,6 +1206,11 @@ JvmtiEnv::GetOwnedMonitorInfo(JavaThread* java_thread, jint* owned_monitor_count
|
||||
GrowableArray<jvmtiMonitorStackDepthInfo*> *owned_monitors_list =
|
||||
new (ResourceObj::C_HEAP, mtServiceability) GrowableArray<jvmtiMonitorStackDepthInfo*>(1, mtServiceability);
|
||||
|
||||
EscapeBarrier eb(true, calling_thread, java_thread);
|
||||
if (!eb.deoptimize_objects(MaxJavaStackTraceDepth)) {
|
||||
return JVMTI_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
// It is only safe to perform the direct operation on the current
|
||||
// thread. All other usage needs to use a direct handshake for safety.
|
||||
if (java_thread == calling_thread) {
|
||||
@ -1251,6 +1256,11 @@ JvmtiEnv::GetOwnedMonitorStackDepthInfo(JavaThread* java_thread, jint* monitor_i
|
||||
GrowableArray<jvmtiMonitorStackDepthInfo*> *owned_monitors_list =
|
||||
new (ResourceObj::C_HEAP, mtServiceability) GrowableArray<jvmtiMonitorStackDepthInfo*>(1, mtServiceability);
|
||||
|
||||
EscapeBarrier eb(true, calling_thread, java_thread);
|
||||
if (!eb.deoptimize_objects(MaxJavaStackTraceDepth)) {
|
||||
return JVMTI_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
// It is only safe to perform the direct operation on the current
|
||||
// thread. All other usage needs to use a direct handshake for safety.
|
||||
if (java_thread == calling_thread) {
|
||||
@ -1666,6 +1676,13 @@ JvmtiEnv::PopFrame(JavaThread* java_thread) {
|
||||
}
|
||||
}
|
||||
|
||||
if (java_thread->frames_to_pop_failed_realloc() > 0) {
|
||||
// VM is in the process of popping the top frame because it has scalar replaced objects which
|
||||
// could not be reallocated on the heap.
|
||||
// Return JVMTI_ERROR_OUT_OF_MEMORY to avoid interfering with the VM.
|
||||
return JVMTI_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
{
|
||||
ResourceMark rm(current_thread);
|
||||
// Check if there are more than one Java frame in this thread, that the top two frames
|
||||
@ -1697,9 +1714,15 @@ JvmtiEnv::PopFrame(JavaThread* java_thread) {
|
||||
}
|
||||
|
||||
// If any of the top 2 frames is a compiled one, need to deoptimize it
|
||||
EscapeBarrier eb(!is_interpreted[0] || !is_interpreted[1], current_thread, java_thread);
|
||||
for (int i = 0; i < 2; i++) {
|
||||
if (!is_interpreted[i]) {
|
||||
Deoptimization::deoptimize_frame(java_thread, frame_sp[i]);
|
||||
// Eagerly reallocate scalar replaced objects.
|
||||
if (!eb.deoptimize_objects(frame_sp[i])) {
|
||||
// Reallocation of scalar replaced objects failed -> return with error
|
||||
return JVMTI_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1306,10 +1306,17 @@ VM_GetAllStackTraces::doit() {
|
||||
// HandleMark must be defined in the caller only.
|
||||
// It is to keep a ret_ob_h handle alive after return to the caller.
|
||||
jvmtiError
|
||||
JvmtiEnvBase::check_top_frame(Thread* current_thread, JavaThread* java_thread,
|
||||
JvmtiEnvBase::check_top_frame(JavaThread* current_thread, JavaThread* java_thread,
|
||||
jvalue value, TosState tos, Handle* ret_ob_h) {
|
||||
ResourceMark rm(current_thread);
|
||||
|
||||
if (java_thread->frames_to_pop_failed_realloc() > 0) {
|
||||
// VM is in the process of popping the top frame because it has scalar replaced objects
|
||||
// which could not be reallocated on the heap.
|
||||
// Return JVMTI_ERROR_OUT_OF_MEMORY to avoid interfering with the VM.
|
||||
return JVMTI_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
vframe *vf = vframeForNoProcess(java_thread, 0);
|
||||
NULL_CHECK(vf, JVMTI_ERROR_NO_MORE_FRAMES);
|
||||
|
||||
@ -1324,6 +1331,12 @@ JvmtiEnvBase::check_top_frame(Thread* current_thread, JavaThread* java_thread,
|
||||
return JVMTI_ERROR_OPAQUE_FRAME;
|
||||
}
|
||||
Deoptimization::deoptimize_frame(java_thread, jvf->fr().id());
|
||||
// Eagerly reallocate scalar replaced objects.
|
||||
EscapeBarrier eb(true, current_thread, java_thread);
|
||||
if (!eb.deoptimize_objects(jvf->fr().id())) {
|
||||
// Reallocation of scalar replaced objects failed -> return with error
|
||||
return JVMTI_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
}
|
||||
|
||||
// Get information about method return type
|
||||
@ -1369,7 +1382,7 @@ JvmtiEnvBase::check_top_frame(Thread* current_thread, JavaThread* java_thread,
|
||||
|
||||
jvmtiError
|
||||
JvmtiEnvBase::force_early_return(JavaThread* java_thread, jvalue value, TosState tos) {
|
||||
Thread* current_thread = Thread::current();
|
||||
JavaThread* current_thread = JavaThread::current();
|
||||
HandleMark hm(current_thread);
|
||||
uint32_t debug_bits = 0;
|
||||
|
||||
|
@ -306,7 +306,7 @@ class JvmtiEnvBase : public CHeapObj<mtInternal> {
|
||||
jobject *monitor_ptr);
|
||||
jvmtiError get_owned_monitors(JavaThread *calling_thread, JavaThread* java_thread,
|
||||
GrowableArray<jvmtiMonitorStackDepthInfo*> *owned_monitors_list);
|
||||
jvmtiError check_top_frame(Thread* current_thread, JavaThread* java_thread,
|
||||
jvmtiError check_top_frame(JavaThread* current_thread, JavaThread* java_thread,
|
||||
jvalue value, TosState tos, Handle* ret_ob_h);
|
||||
jvmtiError force_early_return(JavaThread* java_thread, jvalue value, TosState tos);
|
||||
};
|
||||
|
@ -426,6 +426,7 @@ VM_GetOrSetLocal::VM_GetOrSetLocal(JavaThread* thread, jint depth, jint index, B
|
||||
, _type(type)
|
||||
, _jvf(NULL)
|
||||
, _set(false)
|
||||
, _eb(false, NULL, NULL)
|
||||
, _result(JVMTI_ERROR_NONE)
|
||||
{
|
||||
}
|
||||
@ -440,6 +441,7 @@ VM_GetOrSetLocal::VM_GetOrSetLocal(JavaThread* thread, jint depth, jint index, B
|
||||
, _value(value)
|
||||
, _jvf(NULL)
|
||||
, _set(true)
|
||||
, _eb(type == T_OBJECT, JavaThread::current(), thread)
|
||||
, _result(JVMTI_ERROR_NONE)
|
||||
{
|
||||
}
|
||||
@ -453,6 +455,7 @@ VM_GetOrSetLocal::VM_GetOrSetLocal(JavaThread* thread, JavaThread* calling_threa
|
||||
, _type(T_OBJECT)
|
||||
, _jvf(NULL)
|
||||
, _set(false)
|
||||
, _eb(true, calling_thread, thread)
|
||||
, _result(JVMTI_ERROR_NONE)
|
||||
{
|
||||
}
|
||||
@ -625,8 +628,64 @@ static bool can_be_deoptimized(vframe* vf) {
|
||||
return (vf->is_compiled_frame() && vf->fr().can_be_deoptimized());
|
||||
}
|
||||
|
||||
// Revert optimizations based on escape analysis if this is an access to a local object
|
||||
bool VM_GetOrSetLocal::deoptimize_objects(javaVFrame* jvf) {
|
||||
#if COMPILER2_OR_JVMCI
|
||||
assert(_type == T_OBJECT, "EscapeBarrier should not be active if _type != T_OBJECT");
|
||||
if (_depth < _thread->frames_to_pop_failed_realloc()) {
|
||||
// cannot access frame with failed reallocations
|
||||
_result = JVMTI_ERROR_OUT_OF_MEMORY;
|
||||
return false;
|
||||
}
|
||||
if (can_be_deoptimized(jvf)) {
|
||||
compiledVFrame* cf = compiledVFrame::cast(jvf);
|
||||
if (cf->has_ea_local_in_scope() && !_eb.deoptimize_objects(cf->fr().id())) {
|
||||
// reallocation of scalar replaced objects failed because heap is exhausted
|
||||
_result = JVMTI_ERROR_OUT_OF_MEMORY;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// With this access the object could escape the thread changing its escape state from ArgEscape,
|
||||
// to GlobalEscape so we must deoptimize callers which could have optimized on the escape state.
|
||||
vframe* vf = jvf;
|
||||
do {
|
||||
// move to next physical frame
|
||||
while(!vf->is_top()) {
|
||||
vf = vf->sender();
|
||||
}
|
||||
vf = vf->sender();
|
||||
|
||||
if (vf != NULL && vf->is_compiled_frame()) {
|
||||
compiledVFrame* cvf = compiledVFrame::cast(vf);
|
||||
// Deoptimize objects if arg escape is being passed down the stack.
|
||||
// Note that deoptimizing the frame is not enough because objects need to be relocked
|
||||
if (cvf->arg_escape() && !_eb.deoptimize_objects(cvf->fr().id())) {
|
||||
// reallocation of scalar replaced objects failed because heap is exhausted
|
||||
_result = JVMTI_ERROR_OUT_OF_MEMORY;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} while(vf != NULL && !vf->is_entry_frame());
|
||||
#endif // COMPILER2_OR_JVMCI
|
||||
return true;
|
||||
}
|
||||
|
||||
bool VM_GetOrSetLocal::doit_prologue() {
|
||||
if (_eb.barrier_active()) {
|
||||
_jvf = get_java_vframe();
|
||||
NULL_CHECK(_jvf, false);
|
||||
|
||||
if (!deoptimize_objects(_jvf)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void VM_GetOrSetLocal::doit() {
|
||||
_jvf = get_java_vframe();
|
||||
_jvf = _jvf == NULL ? get_java_vframe() : _jvf;
|
||||
if (_jvf == NULL) {
|
||||
return;
|
||||
};
|
||||
|
@ -32,6 +32,7 @@
|
||||
#include "prims/jvmtiEventController.hpp"
|
||||
#include "prims/jvmtiTrace.hpp"
|
||||
#include "prims/jvmtiUtil.hpp"
|
||||
#include "runtime/escapeBarrier.hpp"
|
||||
#include "runtime/stackValueCollection.hpp"
|
||||
#include "runtime/vmOperations.hpp"
|
||||
#include "utilities/ostream.hpp"
|
||||
@ -321,6 +322,8 @@ class VM_GetOrSetLocal : public VM_Operation {
|
||||
javaVFrame* _jvf;
|
||||
bool _set;
|
||||
|
||||
EscapeBarrier _eb;
|
||||
|
||||
// It is possible to get the receiver out of a non-static native wrapper
|
||||
// frame. Use VM_GetReceiver to do this.
|
||||
virtual bool getting_receiver() const { return false; }
|
||||
@ -331,6 +334,7 @@ class VM_GetOrSetLocal : public VM_Operation {
|
||||
javaVFrame* get_java_vframe();
|
||||
bool check_slot_type_lvt(javaVFrame* vf);
|
||||
bool check_slot_type_no_lvt(javaVFrame* vf);
|
||||
bool deoptimize_objects(javaVFrame* vf);
|
||||
|
||||
public:
|
||||
// Constructor for non-object getter
|
||||
@ -347,6 +351,7 @@ public:
|
||||
jvalue value() { return _value; }
|
||||
jvmtiError result() { return _result; }
|
||||
|
||||
bool doit_prologue();
|
||||
void doit();
|
||||
bool allow_nested_vm_operations() const;
|
||||
const char* name() const { return "get/set locals"; }
|
||||
|
@ -47,6 +47,7 @@
|
||||
#include "prims/jvmtiImpl.hpp"
|
||||
#include "prims/jvmtiTagMap.hpp"
|
||||
#include "runtime/biasedLocking.hpp"
|
||||
#include "runtime/deoptimization.hpp"
|
||||
#include "runtime/frame.inline.hpp"
|
||||
#include "runtime/handles.inline.hpp"
|
||||
#include "runtime/javaCalls.hpp"
|
||||
@ -1482,6 +1483,11 @@ void JvmtiTagMap::iterate_over_heap(jvmtiHeapObjectFilter object_filter,
|
||||
jvmtiHeapObjectCallback heap_object_callback,
|
||||
const void* user_data)
|
||||
{
|
||||
// EA based optimizations on tagged objects are already reverted.
|
||||
EscapeBarrier eb(object_filter == JVMTI_HEAP_OBJECT_UNTAGGED ||
|
||||
object_filter == JVMTI_HEAP_OBJECT_EITHER,
|
||||
JavaThread::current());
|
||||
eb.deoptimize_objects_all_threads();
|
||||
MutexLocker ml(Heap_lock);
|
||||
IterateOverHeapObjectClosure blk(this,
|
||||
klass,
|
||||
@ -1499,6 +1505,9 @@ void JvmtiTagMap::iterate_through_heap(jint heap_filter,
|
||||
const jvmtiHeapCallbacks* callbacks,
|
||||
const void* user_data)
|
||||
{
|
||||
// EA based optimizations on tagged objects are already reverted.
|
||||
EscapeBarrier eb(!(heap_filter & JVMTI_HEAP_FILTER_UNTAGGED), JavaThread::current());
|
||||
eb.deoptimize_objects_all_threads();
|
||||
MutexLocker ml(Heap_lock);
|
||||
IterateThroughHeapObjectClosure blk(this,
|
||||
klass,
|
||||
@ -3246,6 +3255,9 @@ void JvmtiTagMap::iterate_over_reachable_objects(jvmtiHeapRootCallback heap_root
|
||||
jvmtiStackReferenceCallback stack_ref_callback,
|
||||
jvmtiObjectReferenceCallback object_ref_callback,
|
||||
const void* user_data) {
|
||||
JavaThread* jt = JavaThread::current();
|
||||
EscapeBarrier eb(true, jt);
|
||||
eb.deoptimize_objects_all_threads();
|
||||
MutexLocker ml(Heap_lock);
|
||||
BasicHeapWalkContext context(heap_root_callback, stack_ref_callback, object_ref_callback);
|
||||
VM_HeapWalkOperation op(this, Handle(), context, user_data);
|
||||
@ -3273,8 +3285,13 @@ void JvmtiTagMap::follow_references(jint heap_filter,
|
||||
const void* user_data)
|
||||
{
|
||||
oop obj = JNIHandles::resolve(object);
|
||||
Handle initial_object(Thread::current(), obj);
|
||||
|
||||
JavaThread* jt = JavaThread::current();
|
||||
Handle initial_object(jt, obj);
|
||||
// EA based optimizations that are tagged or reachable from initial_object are already reverted.
|
||||
EscapeBarrier eb(initial_object.is_null() &&
|
||||
!(heap_filter & JVMTI_HEAP_FILTER_UNTAGGED),
|
||||
jt);
|
||||
eb.deoptimize_objects_all_threads();
|
||||
MutexLocker ml(Heap_lock);
|
||||
AdvancedHeapWalkContext context(heap_filter, klass, callbacks);
|
||||
VM_HeapWalkOperation op(this, initial_object, context, user_data);
|
||||
|
@ -79,6 +79,7 @@
|
||||
#include "runtime/synchronizer.hpp"
|
||||
#include "runtime/thread.hpp"
|
||||
#include "runtime/threadSMR.hpp"
|
||||
#include "runtime/vframe.hpp"
|
||||
#include "runtime/vm_version.hpp"
|
||||
#include "services/memoryService.hpp"
|
||||
#include "utilities/align.hpp"
|
||||
@ -882,6 +883,19 @@ WB_ENTRY(jint, WB_DeoptimizeFrames(JNIEnv* env, jobject o, jboolean make_not_ent
|
||||
return op.result();
|
||||
WB_END
|
||||
|
||||
WB_ENTRY(jboolean, WB_IsFrameDeoptimized(JNIEnv* env, jobject o, jint depth))
|
||||
bool result = false;
|
||||
if (thread->has_last_Java_frame()) {
|
||||
RegisterMap reg_map(thread);
|
||||
javaVFrame *jvf = thread->last_java_vframe(®_map);
|
||||
for (jint d = 0; d < depth && jvf != NULL; d++) {
|
||||
jvf = jvf->java_sender();
|
||||
}
|
||||
result = jvf != NULL && jvf->fr().is_deoptimized_frame();
|
||||
}
|
||||
return result;
|
||||
WB_END
|
||||
|
||||
WB_ENTRY(void, WB_DeoptimizeAll(JNIEnv* env, jobject o))
|
||||
CodeCache::mark_all_nmethods_for_deoptimization();
|
||||
Deoptimization::deoptimize_all_marked();
|
||||
@ -2426,6 +2440,7 @@ static JNINativeMethod methods[] = {
|
||||
{CC"NMTArenaMalloc", CC"(JJ)V", (void*)&WB_NMTArenaMalloc },
|
||||
#endif // INCLUDE_NMT
|
||||
{CC"deoptimizeFrames", CC"(Z)I", (void*)&WB_DeoptimizeFrames },
|
||||
{CC"isFrameDeoptimized", CC"(I)Z", (void*)&WB_IsFrameDeoptimized},
|
||||
{CC"deoptimizeAll", CC"()V", (void*)&WB_DeoptimizeAll },
|
||||
{CC"deoptimizeMethod0", CC"(Ljava/lang/reflect/Executable;Z)I",
|
||||
(void*)&WB_DeoptimizeMethod },
|
||||
|
@ -48,18 +48,21 @@
|
||||
#include "oops/fieldStreams.inline.hpp"
|
||||
#include "oops/typeArrayOop.inline.hpp"
|
||||
#include "oops/verifyOopClosure.hpp"
|
||||
#include "prims/jvmtiDeferredUpdates.hpp"
|
||||
#include "prims/jvmtiThreadState.hpp"
|
||||
#include "prims/vectorSupport.hpp"
|
||||
#include "prims/methodHandles.hpp"
|
||||
#include "runtime/atomic.hpp"
|
||||
#include "runtime/biasedLocking.hpp"
|
||||
#include "runtime/deoptimization.hpp"
|
||||
#include "runtime/escapeBarrier.hpp"
|
||||
#include "runtime/fieldDescriptor.hpp"
|
||||
#include "runtime/fieldDescriptor.inline.hpp"
|
||||
#include "runtime/frame.inline.hpp"
|
||||
#include "runtime/handles.inline.hpp"
|
||||
#include "runtime/interfaceSupport.inline.hpp"
|
||||
#include "runtime/jniHandles.inline.hpp"
|
||||
#include "runtime/objectMonitor.inline.hpp"
|
||||
#include "runtime/safepointVerifiers.hpp"
|
||||
#include "runtime/sharedRuntime.hpp"
|
||||
#include "runtime/signature.hpp"
|
||||
@ -175,10 +178,15 @@ JRT_END
|
||||
|
||||
#if COMPILER2_OR_JVMCI
|
||||
static bool eliminate_allocations(JavaThread* thread, int exec_mode, CompiledMethod* compiled_method,
|
||||
frame& deoptee, RegisterMap& map, GrowableArray<compiledVFrame*>* chunk) {
|
||||
frame& deoptee, RegisterMap& map, GrowableArray<compiledVFrame*>* chunk,
|
||||
bool& deoptimized_objects) {
|
||||
bool realloc_failures = false;
|
||||
assert (chunk->at(0)->scope() != NULL,"expect only compiled java frames");
|
||||
|
||||
JavaThread* deoptee_thread = chunk->at(0)->thread();
|
||||
assert(exec_mode == Deoptimization::Unpack_none || (deoptee_thread == thread),
|
||||
"a frame can only be deoptimized by the owner thread");
|
||||
|
||||
GrowableArray<ScopeValue*>* objects = chunk->at(0)->scope()->objects();
|
||||
|
||||
// The flag return_oop() indicates call sites which return oop
|
||||
@ -205,15 +213,28 @@ static bool eliminate_allocations(JavaThread* thread, int exec_mode, CompiledMet
|
||||
}
|
||||
}
|
||||
if (objects != NULL) {
|
||||
JRT_BLOCK
|
||||
if (exec_mode == Deoptimization::Unpack_none) {
|
||||
assert(thread->thread_state() == _thread_in_vm, "assumption");
|
||||
Thread* THREAD = thread;
|
||||
// Clear pending OOM if reallocation fails and return true indicating allocation failure
|
||||
realloc_failures = Deoptimization::realloc_objects(thread, &deoptee, &map, objects, CHECK_AND_CLEAR_(true));
|
||||
// Make sure the deoptee frame gets processed after a potential safepoint during
|
||||
// object reallocation. This is necessary because (a) deoptee_thread can be
|
||||
// different from the current thread and (b) the deoptee frame does not need to be
|
||||
// the top frame.
|
||||
StackWatermarkSet::finish_processing(deoptee_thread, NULL /* context */, StackWatermarkKind::gc);
|
||||
deoptimized_objects = true;
|
||||
} else {
|
||||
JRT_BLOCK
|
||||
realloc_failures = Deoptimization::realloc_objects(thread, &deoptee, &map, objects, THREAD);
|
||||
JRT_END
|
||||
JRT_END
|
||||
}
|
||||
bool skip_internal = (compiled_method != NULL) && !compiled_method->is_compiled_by_jvmci();
|
||||
Deoptimization::reassign_fields(&deoptee, &map, objects, realloc_failures, skip_internal);
|
||||
#ifndef PRODUCT
|
||||
if (TraceDeoptimization) {
|
||||
ttyLocker ttyl;
|
||||
tty->print_cr("REALLOC OBJECTS in thread " INTPTR_FORMAT, p2i(thread));
|
||||
tty->print_cr("REALLOC OBJECTS in thread " INTPTR_FORMAT, p2i(deoptee_thread));
|
||||
Deoptimization::print_objects(objects, realloc_failures);
|
||||
}
|
||||
#endif
|
||||
@ -225,7 +246,10 @@ static bool eliminate_allocations(JavaThread* thread, int exec_mode, CompiledMet
|
||||
return realloc_failures;
|
||||
}
|
||||
|
||||
static void eliminate_locks(JavaThread* thread, GrowableArray<compiledVFrame*>* chunk, bool realloc_failures) {
|
||||
static void eliminate_locks(JavaThread* thread, GrowableArray<compiledVFrame*>* chunk, bool realloc_failures,
|
||||
frame& deoptee, int exec_mode, bool& deoptimized_objects) {
|
||||
JavaThread* deoptee_thread = chunk->at(0)->thread();
|
||||
assert(!EscapeBarrier::objs_are_deoptimized(deoptee_thread, deoptee.id()), "must relock just once");
|
||||
assert(thread == Thread::current(), "should be");
|
||||
HandleMark hm(thread);
|
||||
#ifndef PRODUCT
|
||||
@ -236,7 +260,9 @@ static void eliminate_locks(JavaThread* thread, GrowableArray<compiledVFrame*>*
|
||||
assert (cvf->scope() != NULL,"expect only compiled java frames");
|
||||
GrowableArray<MonitorInfo*>* monitors = cvf->monitors();
|
||||
if (monitors->is_nonempty()) {
|
||||
Deoptimization::relock_objects(monitors, thread, realloc_failures);
|
||||
bool relocked = Deoptimization::relock_objects(thread, monitors, deoptee_thread, deoptee,
|
||||
exec_mode, realloc_failures);
|
||||
deoptimized_objects = deoptimized_objects || relocked;
|
||||
#ifndef PRODUCT
|
||||
if (PrintDeoptimizationDetails) {
|
||||
ttyLocker ttyl;
|
||||
@ -247,6 +273,13 @@ static void eliminate_locks(JavaThread* thread, GrowableArray<compiledVFrame*>*
|
||||
first = false;
|
||||
tty->print_cr("RELOCK OBJECTS in thread " INTPTR_FORMAT, p2i(thread));
|
||||
}
|
||||
if (exec_mode == Deoptimization::Unpack_none) {
|
||||
ObjectMonitor* monitor = deoptee_thread->current_waiting_monitor();
|
||||
if (monitor != NULL && (oop)monitor->object() == mi->owner()) {
|
||||
tty->print_cr(" object <" INTPTR_FORMAT "> DEFERRED relocking after wait", p2i(mi->owner()));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (mi->owner_is_scalar_replaced()) {
|
||||
Klass* k = java_lang_Class::as_Klass(mi->owner_klass());
|
||||
tty->print_cr(" failed reallocation for klass %s", k->external_name());
|
||||
@ -260,6 +293,36 @@ static void eliminate_locks(JavaThread* thread, GrowableArray<compiledVFrame*>*
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Deoptimize objects, that is reallocate and relock them, just before they escape through JVMTI.
|
||||
// The given vframes cover one physical frame.
|
||||
bool Deoptimization::deoptimize_objects_internal(JavaThread* thread, GrowableArray<compiledVFrame*>* chunk,
|
||||
bool& realloc_failures) {
|
||||
frame deoptee = chunk->at(0)->fr();
|
||||
JavaThread* deoptee_thread = chunk->at(0)->thread();
|
||||
CompiledMethod* cm = deoptee.cb()->as_compiled_method_or_null();
|
||||
RegisterMap map(chunk->at(0)->register_map());
|
||||
bool deoptimized_objects = false;
|
||||
|
||||
bool const jvmci_enabled = JVMCI_ONLY(UseJVMCICompiler) NOT_JVMCI(false);
|
||||
|
||||
// Reallocate the non-escaping objects and restore their fields.
|
||||
if (jvmci_enabled COMPILER2_PRESENT(|| (DoEscapeAnalysis && EliminateAllocations))) {
|
||||
realloc_failures = eliminate_allocations(thread, Unpack_none, cm, deoptee, map, chunk, deoptimized_objects);
|
||||
}
|
||||
|
||||
// Revoke biases of objects with eliminated locks in the given frame.
|
||||
Deoptimization::revoke_for_object_deoptimization(deoptee_thread, deoptee, &map, thread);
|
||||
|
||||
// MonitorInfo structures used in eliminate_locks are not GC safe.
|
||||
NoSafepointVerifier no_safepoint;
|
||||
|
||||
// Now relock objects if synchronization on them was eliminated.
|
||||
if (jvmci_enabled COMPILER2_PRESENT(|| ((DoEscapeAnalysis || EliminateNestedLocks) && EliminateLocks))) {
|
||||
eliminate_locks(thread, chunk, realloc_failures, deoptee, Unpack_none, deoptimized_objects);
|
||||
}
|
||||
return deoptimized_objects;
|
||||
}
|
||||
#endif // COMPILER2_OR_JVMCI
|
||||
|
||||
// This is factored, since it is both called from a JRT_LEAF (deoptimization) and a JRT_ENTRY (uncommon_trap)
|
||||
@ -318,7 +381,8 @@ Deoptimization::UnrollBlock* Deoptimization::fetch_unroll_info_helper(JavaThread
|
||||
// Reallocate the non-escaping objects and restore their fields. Then
|
||||
// relock objects if synchronization on them was eliminated.
|
||||
if (jvmci_enabled COMPILER2_PRESENT( || (DoEscapeAnalysis && EliminateAllocations) )) {
|
||||
realloc_failures = eliminate_allocations(thread, exec_mode, cm, deoptee, map, chunk);
|
||||
bool unused;
|
||||
realloc_failures = eliminate_allocations(thread, exec_mode, cm, deoptee, map, chunk, unused);
|
||||
}
|
||||
#endif // COMPILER2_OR_JVMCI
|
||||
|
||||
@ -333,8 +397,10 @@ Deoptimization::UnrollBlock* Deoptimization::fetch_unroll_info_helper(JavaThread
|
||||
NoSafepointVerifier no_safepoint;
|
||||
|
||||
#if COMPILER2_OR_JVMCI
|
||||
if (jvmci_enabled COMPILER2_PRESENT( || ((DoEscapeAnalysis || EliminateNestedLocks) && EliminateLocks) )) {
|
||||
eliminate_locks(thread, chunk, realloc_failures);
|
||||
if ((jvmci_enabled COMPILER2_PRESENT( || ((DoEscapeAnalysis || EliminateNestedLocks) && EliminateLocks) ))
|
||||
&& !EscapeBarrier::objs_are_deoptimized(thread, deoptee.id())) {
|
||||
bool unused;
|
||||
eliminate_locks(thread, chunk, realloc_failures, deoptee, exec_mode, unused);
|
||||
}
|
||||
#endif // COMPILER2_OR_JVMCI
|
||||
|
||||
@ -365,28 +431,7 @@ Deoptimization::UnrollBlock* Deoptimization::fetch_unroll_info_helper(JavaThread
|
||||
// added by jvmti then we can free up that structure as the data is now in the
|
||||
// vframeArray
|
||||
|
||||
if (thread->deferred_locals() != NULL) {
|
||||
GrowableArray<jvmtiDeferredLocalVariableSet*>* list = thread->deferred_locals();
|
||||
int i = 0;
|
||||
do {
|
||||
// Because of inlining we could have multiple vframes for a single frame
|
||||
// and several of the vframes could have deferred writes. Find them all.
|
||||
if (list->at(i)->id() == array->original().id()) {
|
||||
jvmtiDeferredLocalVariableSet* dlv = list->at(i);
|
||||
list->remove_at(i);
|
||||
// individual jvmtiDeferredLocalVariableSet are CHeapObj's
|
||||
delete dlv;
|
||||
} else {
|
||||
i++;
|
||||
}
|
||||
} while ( i < list->length() );
|
||||
if (list->length() == 0) {
|
||||
thread->set_deferred_locals(NULL);
|
||||
// free the list and elements back to C heap.
|
||||
delete list;
|
||||
}
|
||||
|
||||
}
|
||||
JvmtiDeferredUpdates::delete_updates_for_frame(thread, array->original().id());
|
||||
|
||||
// Compute the caller frame based on the sender sp of stub_frame and stored frame sizes info.
|
||||
CodeBlob* cb = stub_frame.cb();
|
||||
@ -1380,11 +1425,14 @@ void Deoptimization::reassign_fields(frame* fr, RegisterMap* reg_map, GrowableAr
|
||||
|
||||
|
||||
// relock objects for which synchronization was eliminated
|
||||
void Deoptimization::relock_objects(GrowableArray<MonitorInfo*>* monitors, JavaThread* thread, bool realloc_failures) {
|
||||
bool Deoptimization::relock_objects(JavaThread* thread, GrowableArray<MonitorInfo*>* monitors,
|
||||
JavaThread* deoptee_thread, frame& fr, int exec_mode, bool realloc_failures) {
|
||||
bool relocked_objects = false;
|
||||
for (int i = 0; i < monitors->length(); i++) {
|
||||
MonitorInfo* mon_info = monitors->at(i);
|
||||
if (mon_info->eliminated()) {
|
||||
assert(!mon_info->owner_is_scalar_replaced() || realloc_failures, "reallocation was missed");
|
||||
relocked_objects = true;
|
||||
if (!mon_info->owner_is_scalar_replaced()) {
|
||||
Handle obj(thread, mon_info->owner());
|
||||
markWord mark = obj->mark();
|
||||
@ -1393,17 +1441,37 @@ void Deoptimization::relock_objects(GrowableArray<MonitorInfo*>* monitors, JavaT
|
||||
// Also the deoptimized method may called methods with synchronization
|
||||
// where the thread-local object is bias locked to the current thread.
|
||||
assert(mark.is_biased_anonymously() ||
|
||||
mark.biased_locker() == thread, "should be locked to current thread");
|
||||
mark.biased_locker() == deoptee_thread, "should be locked to current thread");
|
||||
// Reset mark word to unbiased prototype.
|
||||
markWord unbiased_prototype = markWord::prototype().set_age(mark.age());
|
||||
obj->set_mark(unbiased_prototype);
|
||||
} else if (exec_mode == Unpack_none) {
|
||||
if (mark.has_locker() && fr.sp() > (intptr_t*)mark.locker()) {
|
||||
// With exec_mode == Unpack_none obj may be thread local and locked in
|
||||
// a callee frame. In this case the bias was revoked before in revoke_for_object_deoptimization().
|
||||
// Make the lock in the callee a recursive lock and restore the displaced header.
|
||||
markWord dmw = mark.displaced_mark_helper();
|
||||
mark.locker()->set_displaced_header(markWord::encode((BasicLock*) NULL));
|
||||
obj->set_mark(dmw);
|
||||
}
|
||||
if (mark.has_monitor()) {
|
||||
// defer relocking if the deoptee thread is currently waiting for obj
|
||||
ObjectMonitor* waiting_monitor = deoptee_thread->current_waiting_monitor();
|
||||
if (waiting_monitor != NULL && (oop)waiting_monitor->object() == obj()) {
|
||||
assert(fr.is_deoptimized_frame(), "frame must be scheduled for deoptimization");
|
||||
mon_info->lock()->set_displaced_header(markWord::unused_mark());
|
||||
JvmtiDeferredUpdates::inc_relock_count_after_wait(deoptee_thread);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
BasicLock* lock = mon_info->lock();
|
||||
ObjectSynchronizer::enter(obj, lock, thread);
|
||||
ObjectSynchronizer::enter(obj, lock, deoptee_thread);
|
||||
assert(mon_info->owner()->is_locked(), "object must be locked now");
|
||||
}
|
||||
}
|
||||
}
|
||||
return relocked_objects;
|
||||
}
|
||||
|
||||
|
||||
@ -1521,18 +1589,22 @@ void Deoptimization::pop_frames_failed_reallocs(JavaThread* thread, vframeArray*
|
||||
}
|
||||
#endif
|
||||
|
||||
static void collect_monitors(compiledVFrame* cvf, GrowableArray<Handle>* objects_to_revoke) {
|
||||
static void collect_monitors(compiledVFrame* cvf, GrowableArray<Handle>* objects_to_revoke,
|
||||
bool only_eliminated) {
|
||||
GrowableArray<MonitorInfo*>* monitors = cvf->monitors();
|
||||
Thread* thread = Thread::current();
|
||||
for (int i = 0; i < monitors->length(); i++) {
|
||||
MonitorInfo* mon_info = monitors->at(i);
|
||||
if (!mon_info->eliminated() && mon_info->owner() != NULL) {
|
||||
if (mon_info->eliminated() == only_eliminated &&
|
||||
!mon_info->owner_is_scalar_replaced() &&
|
||||
mon_info->owner() != NULL) {
|
||||
objects_to_revoke->append(Handle(thread, mon_info->owner()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void get_monitors_from_stack(GrowableArray<Handle>* objects_to_revoke, JavaThread* thread, frame fr, RegisterMap* map) {
|
||||
static void get_monitors_from_stack(GrowableArray<Handle>* objects_to_revoke, JavaThread* thread,
|
||||
frame fr, RegisterMap* map, bool only_eliminated) {
|
||||
// Unfortunately we don't have a RegisterMap available in most of
|
||||
// the places we want to call this routine so we need to walk the
|
||||
// stack again to update the register map.
|
||||
@ -1552,10 +1624,10 @@ static void get_monitors_from_stack(GrowableArray<Handle>* objects_to_revoke, Ja
|
||||
compiledVFrame* cvf = compiledVFrame::cast(vf);
|
||||
// Revoke monitors' biases in all scopes
|
||||
while (!cvf->is_top()) {
|
||||
collect_monitors(cvf, objects_to_revoke);
|
||||
collect_monitors(cvf, objects_to_revoke, only_eliminated);
|
||||
cvf = compiledVFrame::cast(cvf->sender());
|
||||
}
|
||||
collect_monitors(cvf, objects_to_revoke);
|
||||
collect_monitors(cvf, objects_to_revoke, only_eliminated);
|
||||
}
|
||||
|
||||
void Deoptimization::revoke_from_deopt_handler(JavaThread* thread, frame fr, RegisterMap* map) {
|
||||
@ -1566,7 +1638,7 @@ void Deoptimization::revoke_from_deopt_handler(JavaThread* thread, frame fr, Reg
|
||||
ResourceMark rm(thread);
|
||||
HandleMark hm(thread);
|
||||
GrowableArray<Handle>* objects_to_revoke = new GrowableArray<Handle>();
|
||||
get_monitors_from_stack(objects_to_revoke, thread, fr, map);
|
||||
get_monitors_from_stack(objects_to_revoke, thread, fr, map, false);
|
||||
|
||||
int len = objects_to_revoke->length();
|
||||
for (int i = 0; i < len; i++) {
|
||||
@ -1576,6 +1648,41 @@ void Deoptimization::revoke_from_deopt_handler(JavaThread* thread, frame fr, Reg
|
||||
}
|
||||
}
|
||||
|
||||
// Revoke the bias of objects with eliminated locking to prepare subsequent relocking.
|
||||
void Deoptimization::revoke_for_object_deoptimization(JavaThread* deoptee_thread, frame fr,
|
||||
RegisterMap* map, JavaThread* thread) {
|
||||
if (!UseBiasedLocking) {
|
||||
return;
|
||||
}
|
||||
GrowableArray<Handle>* objects_to_revoke = new GrowableArray<Handle>();
|
||||
if (deoptee_thread != thread) {
|
||||
// Process stack of deoptee thread as we will access oops during object deoptimization.
|
||||
StackWatermarkSet::start_processing(deoptee_thread, StackWatermarkKind::gc);
|
||||
}
|
||||
// Collect monitors but only those with eliminated locking.
|
||||
get_monitors_from_stack(objects_to_revoke, deoptee_thread, fr, map, true);
|
||||
|
||||
int len = objects_to_revoke->length();
|
||||
for (int i = 0; i < len; i++) {
|
||||
oop obj = (objects_to_revoke->at(i))();
|
||||
markWord mark = obj->mark();
|
||||
if (!mark.has_bias_pattern() ||
|
||||
mark.is_biased_anonymously() || // eliminated locking does not bias an object if it wasn't before
|
||||
!obj->klass()->prototype_header().has_bias_pattern() || // bulk revoke ignores eliminated monitors
|
||||
(obj->klass()->prototype_header().bias_epoch() != mark.bias_epoch())) { // bulk rebias ignores eliminated monitors
|
||||
// We reach here regularly if there's just eliminated locking on obj.
|
||||
// We must not call BiasedLocking::revoke_own_lock() in this case, as we
|
||||
// would hit assertions because it is a prerequisite that there has to be
|
||||
// non-eliminated locking on obj by deoptee_thread.
|
||||
// Luckily we don't have to revoke here because obj has to be a
|
||||
// non-escaping obj and can be relocked without revoking the bias. See
|
||||
// Deoptimization::relock_objects().
|
||||
continue;
|
||||
}
|
||||
BiasedLocking::revoke(objects_to_revoke->at(i), thread);
|
||||
assert(!objects_to_revoke->at(i)->mark().has_bias_pattern(), "biases should be revoked by now");
|
||||
}
|
||||
}
|
||||
|
||||
void Deoptimization::deoptimize_single_frame(JavaThread* thread, frame fr, Deoptimization::DeoptReason reason) {
|
||||
assert(fr.can_be_deoptimized(), "checking frame type");
|
||||
@ -2622,6 +2729,7 @@ void Deoptimization::print_statistics() {
|
||||
if (xtty != NULL) xtty->tail("statistics");
|
||||
}
|
||||
}
|
||||
|
||||
#else // COMPILER2_OR_JVMCI
|
||||
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 1997, 2019, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 1997, 2020, 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
|
||||
@ -41,6 +41,7 @@ template<class E> class GrowableArray;
|
||||
|
||||
class Deoptimization : AllStatic {
|
||||
friend class VMStructs;
|
||||
friend class EscapeBarrier;
|
||||
|
||||
public:
|
||||
// What condition caused the deoptimization?
|
||||
@ -134,7 +135,8 @@ class Deoptimization : AllStatic {
|
||||
Unpack_exception = 1, // exception is pending
|
||||
Unpack_uncommon_trap = 2, // redo last byte code (C2 only)
|
||||
Unpack_reexecute = 3, // reexecute bytecode (C1 only)
|
||||
Unpack_LIMIT = 4
|
||||
Unpack_none = 4, // not deoptimizing the frame, just reallocating/relocking for JVMTI
|
||||
Unpack_LIMIT = 5
|
||||
};
|
||||
|
||||
#if INCLUDE_JVMCI
|
||||
@ -152,6 +154,9 @@ class Deoptimization : AllStatic {
|
||||
// Revoke biased locks at deopt.
|
||||
static void revoke_from_deopt_handler(JavaThread* thread, frame fr, RegisterMap* map);
|
||||
|
||||
static void revoke_for_object_deoptimization(JavaThread* deoptee_thread, frame fr,
|
||||
RegisterMap* map, JavaThread* thread);
|
||||
|
||||
public:
|
||||
// Deoptimizes a frame lazily. Deopt happens on return to the frame.
|
||||
static void deoptimize(JavaThread* thread, frame fr, DeoptReason reason = Reason_constraint);
|
||||
@ -166,6 +171,11 @@ class Deoptimization : AllStatic {
|
||||
static void deoptimize_single_frame(JavaThread* thread, frame fr, DeoptReason reason);
|
||||
|
||||
#if COMPILER2_OR_JVMCI
|
||||
// Deoptimize objects, that is reallocate and relock them, just before they
|
||||
// escape through JVMTI. The given vframes cover one physical frame.
|
||||
static bool deoptimize_objects_internal(JavaThread* thread, GrowableArray<compiledVFrame*>* chunk,
|
||||
bool& realloc_failures);
|
||||
|
||||
public:
|
||||
|
||||
// Support for restoring non-escaping objects
|
||||
@ -173,7 +183,8 @@ class Deoptimization : AllStatic {
|
||||
static void reassign_type_array_elements(frame* fr, RegisterMap* reg_map, ObjectValue* sv, typeArrayOop obj, BasicType type);
|
||||
static void reassign_object_array_elements(frame* fr, RegisterMap* reg_map, ObjectValue* sv, objArrayOop obj);
|
||||
static void reassign_fields(frame* fr, RegisterMap* reg_map, GrowableArray<ScopeValue*>* objects, bool realloc_failures, bool skip_internal);
|
||||
static void relock_objects(GrowableArray<MonitorInfo*>* monitors, JavaThread* thread, bool realloc_failures);
|
||||
static bool relock_objects(JavaThread* thread, GrowableArray<MonitorInfo*>* monitors,
|
||||
JavaThread* deoptee_thread, frame& fr, int exec_mode, bool realloc_failures);
|
||||
static void pop_frames_failed_reallocs(JavaThread* thread, vframeArray* array);
|
||||
NOT_PRODUCT(static void print_objects(GrowableArray<ScopeValue*>* objects, bool realloc_failures);)
|
||||
#endif // COMPILER2_OR_JVMCI
|
||||
@ -465,6 +476,7 @@ class Deoptimization : AllStatic {
|
||||
static void update_method_data_from_interpreter(MethodData* trap_mdo, int trap_bci, int reason);
|
||||
};
|
||||
|
||||
|
||||
class DeoptimizationMarker : StackObj { // for profiling
|
||||
static bool _is_active;
|
||||
public:
|
||||
|
353
src/hotspot/share/runtime/escapeBarrier.cpp
Normal file
353
src/hotspot/share/runtime/escapeBarrier.cpp
Normal file
@ -0,0 +1,353 @@
|
||||
/*
|
||||
* Copyright (c) 1997, 2020, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2020 SAP SE. 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/scopeDesc.hpp"
|
||||
#include "memory/allocation.hpp"
|
||||
#include "prims/jvmtiDeferredUpdates.hpp"
|
||||
#include "runtime/deoptimization.hpp"
|
||||
#include "runtime/escapeBarrier.hpp"
|
||||
#include "runtime/frame.inline.hpp"
|
||||
#include "runtime/handles.hpp"
|
||||
#include "runtime/handshake.hpp"
|
||||
#include "runtime/interfaceSupport.inline.hpp"
|
||||
#include "runtime/mutexLocker.hpp"
|
||||
#include "runtime/registerMap.hpp"
|
||||
#include "runtime/stackValue.hpp"
|
||||
#include "runtime/stackValueCollection.hpp"
|
||||
#include "runtime/threadSMR.hpp"
|
||||
#include "runtime/vframe.hpp"
|
||||
#include "runtime/vframe_hp.hpp"
|
||||
#include "utilities/debug.hpp"
|
||||
#include "utilities/globalDefinitions.hpp"
|
||||
#include "utilities/macros.hpp"
|
||||
|
||||
#if COMPILER2_OR_JVMCI
|
||||
|
||||
// Returns true iff objects were reallocated and relocked because of access through JVMTI
|
||||
bool EscapeBarrier::objs_are_deoptimized(JavaThread* thread, intptr_t* fr_id) {
|
||||
// first/oldest update holds the flag
|
||||
GrowableArray<jvmtiDeferredLocalVariableSet*>* list = JvmtiDeferredUpdates::deferred_locals(thread);
|
||||
bool result = false;
|
||||
if (list != NULL) {
|
||||
for (int i = 0; i < list->length(); i++) {
|
||||
if (list->at(i)->matches(fr_id)) {
|
||||
result = list->at(i)->objects_are_deoptimized();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// Object references of frames up to the given depth are about to be
|
||||
// accessed. Frames with optimizations based on escape state that is potentially
|
||||
// changed by the accesses need to be deoptimized and the referenced objects
|
||||
// need to be reallocated and relocked. Up to depth this is done for frames
|
||||
// with not escaping objects in scope. For deeper frames it is done only if
|
||||
// they pass not escaping objects as arguments because they potentially escape
|
||||
// from callee frames within the given depth.
|
||||
// The search for deeper frames is ended if an entry frame is found because
|
||||
// arguments to native methods are considered to escape globally.
|
||||
bool EscapeBarrier::deoptimize_objects(int depth) {
|
||||
if (barrier_active() && deoptee_thread()->has_last_Java_frame()) {
|
||||
assert(calling_thread() == Thread::current(), "should be");
|
||||
ResourceMark rm(calling_thread());
|
||||
HandleMark hm(calling_thread());
|
||||
RegisterMap reg_map(deoptee_thread(), false /* update_map */, false /* process_frames */);
|
||||
vframe* vf = deoptee_thread()->last_java_vframe(®_map);
|
||||
int cur_depth = 0;
|
||||
while (vf != NULL && ((cur_depth <= depth) || !vf->is_entry_frame())) {
|
||||
if (vf->is_compiled_frame()) {
|
||||
compiledVFrame* cvf = compiledVFrame::cast(vf);
|
||||
// Deoptimize frame and local objects if any exist.
|
||||
// If cvf is deeper than depth, then we deoptimize iff local objects are passed as args.
|
||||
bool should_deopt = cur_depth <= depth ? cvf->has_ea_local_in_scope() : cvf->arg_escape();
|
||||
if (should_deopt && !deoptimize_objects(cvf->fr().id())) {
|
||||
// reallocation of scalar replaced objects failed because heap is exhausted
|
||||
return false;
|
||||
}
|
||||
|
||||
// move to top frame
|
||||
while(!vf->is_top()) {
|
||||
cur_depth++;
|
||||
vf = vf->sender();
|
||||
}
|
||||
}
|
||||
|
||||
// move to next physical frame
|
||||
cur_depth++;
|
||||
vf = vf->sender();
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool EscapeBarrier::deoptimize_objects_all_threads() {
|
||||
if (!barrier_active()) return true;
|
||||
ResourceMark rm(calling_thread());
|
||||
for (JavaThreadIteratorWithHandle jtiwh; JavaThread *jt = jtiwh.next(); ) {
|
||||
if (jt->has_last_Java_frame()) {
|
||||
RegisterMap reg_map(jt, false /* update_map */, false /* process_frames */);
|
||||
vframe* vf = jt->last_java_vframe(®_map);
|
||||
assert(jt->frame_anchor()->walkable(),
|
||||
"The stack of JavaThread " PTR_FORMAT " is not walkable. Thread state is %d",
|
||||
p2i(jt), jt->thread_state());
|
||||
while (vf != NULL) {
|
||||
if (vf->is_compiled_frame()) {
|
||||
compiledVFrame* cvf = compiledVFrame::cast(vf);
|
||||
if ((cvf->has_ea_local_in_scope() || cvf->arg_escape()) &&
|
||||
!deoptimize_objects_internal(jt, cvf->fr().id())) {
|
||||
return false; // reallocation failure
|
||||
}
|
||||
// move to top frame
|
||||
while(!vf->is_top()) {
|
||||
vf = vf->sender();
|
||||
}
|
||||
}
|
||||
// move to next physical frame
|
||||
vf = vf->sender();
|
||||
}
|
||||
}
|
||||
}
|
||||
return true; // success
|
||||
}
|
||||
|
||||
bool EscapeBarrier::_deoptimizing_objects_for_all_threads = false;
|
||||
bool EscapeBarrier::_self_deoptimization_in_progress = false;
|
||||
|
||||
class EscapeBarrierSuspendHandshake : public HandshakeClosure {
|
||||
public:
|
||||
EscapeBarrierSuspendHandshake(const char* name) :
|
||||
HandshakeClosure(name) { }
|
||||
void do_thread(Thread* th) { }
|
||||
};
|
||||
|
||||
void EscapeBarrier::sync_and_suspend_one() {
|
||||
assert(_calling_thread != NULL, "calling thread must not be NULL");
|
||||
assert(_deoptee_thread != NULL, "deoptee thread must not be NULL");
|
||||
assert(barrier_active(), "should not call");
|
||||
|
||||
// Sync with other threads that might be doing deoptimizations
|
||||
{
|
||||
// Need to switch to _thread_blocked for the wait() call
|
||||
ThreadBlockInVM tbivm(_calling_thread);
|
||||
MonitorLocker ml(_calling_thread, EscapeBarrier_lock, Mutex::_no_safepoint_check_flag);
|
||||
while (_self_deoptimization_in_progress || _deoptee_thread->is_obj_deopt_suspend()) {
|
||||
ml.wait();
|
||||
}
|
||||
|
||||
if (self_deopt()) {
|
||||
_self_deoptimization_in_progress = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// set suspend flag for target thread
|
||||
_deoptee_thread->set_obj_deopt_flag();
|
||||
}
|
||||
|
||||
// Use a handshake to synchronize with the target thread.
|
||||
EscapeBarrierSuspendHandshake sh("EscapeBarrierSuspendOne");
|
||||
Handshake::execute(&sh, _deoptee_thread);
|
||||
assert(!_deoptee_thread->has_last_Java_frame() || _deoptee_thread->frame_anchor()->walkable(),
|
||||
"stack should be walkable now");
|
||||
}
|
||||
|
||||
void EscapeBarrier::sync_and_suspend_all() {
|
||||
assert(barrier_active(), "should not call");
|
||||
assert(_calling_thread != NULL, "calling thread must not be NULL");
|
||||
assert(all_threads(), "sanity");
|
||||
|
||||
// Sync with other threads that might be doing deoptimizations
|
||||
{
|
||||
// Need to switch to _thread_blocked for the wait() call
|
||||
ThreadBlockInVM tbivm(_calling_thread);
|
||||
MonitorLocker ml(_calling_thread, EscapeBarrier_lock, Mutex::_no_safepoint_check_flag);
|
||||
|
||||
bool deopt_in_progress;
|
||||
do {
|
||||
deopt_in_progress = _self_deoptimization_in_progress;
|
||||
for (JavaThreadIteratorWithHandle jtiwh; JavaThread *jt = jtiwh.next(); ) {
|
||||
deopt_in_progress = (deopt_in_progress || jt->is_obj_deopt_suspend());
|
||||
if (deopt_in_progress) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (deopt_in_progress) {
|
||||
ml.wait(); // then check again
|
||||
}
|
||||
} while(deopt_in_progress);
|
||||
|
||||
_self_deoptimization_in_progress = true;
|
||||
_deoptimizing_objects_for_all_threads = true;
|
||||
|
||||
// We set the suspend flags before executing the handshake because then the
|
||||
// setting will be visible after leaving the _thread_blocked state in
|
||||
// JavaThread::wait_for_object_deoptimization(). If we set the flags in the
|
||||
// handshake then the read must happen after the safepoint/handshake poll.
|
||||
for (JavaThreadIteratorWithHandle jtiwh; JavaThread *jt = jtiwh.next(); ) {
|
||||
if (jt->is_Java_thread() && !jt->is_hidden_from_external_view() && (jt != _calling_thread)) {
|
||||
jt->set_obj_deopt_flag();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Use a handshake to synchronize with the other threads.
|
||||
EscapeBarrierSuspendHandshake sh("EscapeBarrierSuspendAll");
|
||||
Handshake::execute(&sh);
|
||||
#ifdef ASSERT
|
||||
for (JavaThreadIteratorWithHandle jtiwh; JavaThread *jt = jtiwh.next(); ) {
|
||||
if (jt->is_hidden_from_external_view()) continue;
|
||||
assert(!jt->has_last_Java_frame() || jt->frame_anchor()->walkable(),
|
||||
"The stack of JavaThread " PTR_FORMAT " is not walkable. Thread state is %d",
|
||||
p2i(jt), jt->thread_state());
|
||||
}
|
||||
#endif // ASSERT
|
||||
}
|
||||
|
||||
void EscapeBarrier::resume_one() {
|
||||
assert(barrier_active(), "should not call");
|
||||
assert(!all_threads(), "use resume_all()");
|
||||
MonitorLocker ml(_calling_thread, EscapeBarrier_lock, Mutex::_no_safepoint_check_flag);
|
||||
if (self_deopt()) {
|
||||
assert(_self_deoptimization_in_progress, "incorrect synchronization");
|
||||
_self_deoptimization_in_progress = false;
|
||||
} else {
|
||||
_deoptee_thread->clear_obj_deopt_flag();
|
||||
}
|
||||
ml.notify_all();
|
||||
}
|
||||
|
||||
void EscapeBarrier::resume_all() {
|
||||
assert(barrier_active(), "should not call");
|
||||
assert(all_threads(), "use resume_one()");
|
||||
MonitorLocker ml(_calling_thread, EscapeBarrier_lock, Mutex::_no_safepoint_check_flag);
|
||||
assert(_self_deoptimization_in_progress, "incorrect synchronization");
|
||||
_deoptimizing_objects_for_all_threads = false;
|
||||
_self_deoptimization_in_progress = false;
|
||||
for (JavaThreadIteratorWithHandle jtiwh; JavaThread *jt = jtiwh.next(); ) {
|
||||
jt->clear_obj_deopt_flag();
|
||||
}
|
||||
ml.notify_all();
|
||||
}
|
||||
|
||||
void EscapeBarrier::thread_added(JavaThread* jt) {
|
||||
if (!jt->is_hidden_from_external_view()) {
|
||||
MutexLocker ml(EscapeBarrier_lock, Mutex::_no_safepoint_check_flag);
|
||||
if (_deoptimizing_objects_for_all_threads) {
|
||||
jt->set_obj_deopt_flag();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void EscapeBarrier::thread_removed(JavaThread* jt) {
|
||||
MonitorLocker ml(EscapeBarrier_lock, Mutex::_no_safepoint_check_flag);
|
||||
if (jt->is_obj_deopt_suspend()) {
|
||||
// jt terminated before it self suspended.
|
||||
// Other threads might be waiting to perform deoptimizations for it.
|
||||
jt->clear_obj_deopt_flag();
|
||||
ml.notify_all();
|
||||
}
|
||||
}
|
||||
|
||||
// Remember that objects were reallocated and relocked for the compiled frame with the given id
|
||||
static void set_objs_are_deoptimized(JavaThread* thread, intptr_t* fr_id) {
|
||||
// set in first/oldest update
|
||||
GrowableArray<jvmtiDeferredLocalVariableSet*>* list =
|
||||
JvmtiDeferredUpdates::deferred_locals(thread);
|
||||
DEBUG_ONLY(bool found = false);
|
||||
if (list != NULL) {
|
||||
for (int i = 0; i < list->length(); i++) {
|
||||
if (list->at(i)->matches(fr_id)) {
|
||||
DEBUG_ONLY(found = true);
|
||||
list->at(i)->set_objs_are_deoptimized();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
assert(found, "variable set should exist at least for one vframe");
|
||||
}
|
||||
|
||||
// Deoptimize the given frame and deoptimize objects with optimizations based on
|
||||
// escape analysis, i.e. reallocate scalar replaced objects on the heap and
|
||||
// relock objects if locking has been eliminated.
|
||||
// Deoptimized objects are kept as JVMTI deferred updates until the compiled
|
||||
// frame is replaced with interpreter frames. Returns false iff at least one
|
||||
// reallocation failed.
|
||||
bool EscapeBarrier::deoptimize_objects_internal(JavaThread* deoptee, intptr_t* fr_id) {
|
||||
if (!barrier_active()) return true;
|
||||
|
||||
JavaThread* ct = calling_thread();
|
||||
bool realloc_failures = false;
|
||||
|
||||
if (!objs_are_deoptimized(deoptee, fr_id)) {
|
||||
// Make sure the frame identified by fr_id is deoptimized and fetch its last vframe
|
||||
compiledVFrame* last_cvf;
|
||||
bool fr_is_deoptimized;
|
||||
do {
|
||||
if (!self_deopt()) {
|
||||
// Process stack of deoptee thread as we will access oops during object deoptimization.
|
||||
StackWatermarkSet::start_processing(deoptee, StackWatermarkKind::gc);
|
||||
}
|
||||
StackFrameStream fst(deoptee, true /* update */, true /* process_frames */);
|
||||
while (fst.current()->id() != fr_id && !fst.is_done()) {
|
||||
fst.next();
|
||||
}
|
||||
assert(fst.current()->id() == fr_id, "frame not found");
|
||||
assert(fst.current()->is_compiled_frame(),
|
||||
"only compiled frames can contain stack allocated objects");
|
||||
fr_is_deoptimized = fst.current()->is_deoptimized_frame();
|
||||
if (!fr_is_deoptimized) {
|
||||
// Execution must not continue in the compiled method, so we deoptimize the frame.
|
||||
Deoptimization::deoptimize_frame(deoptee, fr_id);
|
||||
} else {
|
||||
last_cvf = compiledVFrame::cast(vframe::new_vframe(fst.current(), fst.register_map(), deoptee));
|
||||
}
|
||||
} while(!fr_is_deoptimized);
|
||||
|
||||
// collect inlined frames
|
||||
compiledVFrame* cvf = last_cvf;
|
||||
GrowableArray<compiledVFrame*>* vfs = new GrowableArray<compiledVFrame*>(10);
|
||||
while (!cvf->is_top()) {
|
||||
vfs->push(cvf);
|
||||
cvf = compiledVFrame::cast(cvf->sender());
|
||||
}
|
||||
vfs->push(cvf);
|
||||
|
||||
// reallocate and relock optimized objects
|
||||
bool deoptimized_objects = Deoptimization::deoptimize_objects_internal(ct, vfs, realloc_failures);
|
||||
if (!realloc_failures && deoptimized_objects) {
|
||||
// now do the updates
|
||||
for (int frame_index = 0; frame_index < vfs->length(); frame_index++) {
|
||||
cvf = vfs->at(frame_index);
|
||||
cvf->create_deferred_updates_after_object_deoptimization();
|
||||
}
|
||||
set_objs_are_deoptimized(deoptee, fr_id);
|
||||
}
|
||||
}
|
||||
return !realloc_failures;
|
||||
}
|
||||
|
||||
#endif // COMPILER2_OR_JVMCI
|
139
src/hotspot/share/runtime/escapeBarrier.hpp
Normal file
139
src/hotspot/share/runtime/escapeBarrier.hpp
Normal file
@ -0,0 +1,139 @@
|
||||
/*
|
||||
* Copyright (c) 1997, 2020, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2020 SAP SE. 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_RUNTIME_ESCAPEBARRIER_HPP
|
||||
#define SHARE_RUNTIME_ESCAPEBARRIER_HPP
|
||||
|
||||
#include "memory/allocation.hpp"
|
||||
#include "runtime/globals.hpp"
|
||||
#include "utilities/macros.hpp"
|
||||
|
||||
class JavaThread;
|
||||
|
||||
// EscapeBarriers should be put on execution paths where JVMTI agents can access object
|
||||
// references held by java threads.
|
||||
// They provide means to revert optimizations based on escape analysis in a well synchronized manner
|
||||
// just before local references escape through JVMTI.
|
||||
|
||||
class EscapeBarrier : StackObj {
|
||||
|
||||
#if COMPILER2_OR_JVMCI
|
||||
JavaThread* const _calling_thread;
|
||||
JavaThread* const _deoptee_thread;
|
||||
bool const _barrier_active;
|
||||
|
||||
static bool _deoptimizing_objects_for_all_threads;
|
||||
static bool _self_deoptimization_in_progress;
|
||||
|
||||
// Suspending is necessary because the target thread's stack must be walked and
|
||||
// object reallocation is not possible in a handshake or at a safepoint.
|
||||
// Suspending is based on handshakes. It is sufficient if the target thread(s)
|
||||
// cannot return to executing bytecodes. Acquiring a lock is ok. Leaving a
|
||||
// safepoint/handshake safe state is not ok.
|
||||
// See also JavaThread::wait_for_object_deoptimization().
|
||||
void sync_and_suspend_one();
|
||||
void sync_and_suspend_all();
|
||||
void resume_one();
|
||||
void resume_all();
|
||||
|
||||
// Deoptimize the given frame and deoptimize objects with optimizations based on escape analysis.
|
||||
bool deoptimize_objects_internal(JavaThread* deoptee, intptr_t* fr_id);
|
||||
|
||||
public:
|
||||
// Revert ea based optimizations for given deoptee thread
|
||||
EscapeBarrier(bool barrier_active, JavaThread* calling_thread, JavaThread* deoptee_thread)
|
||||
: _calling_thread(calling_thread), _deoptee_thread(deoptee_thread),
|
||||
_barrier_active(barrier_active && (JVMCI_ONLY(UseJVMCICompiler) NOT_JVMCI(false)
|
||||
COMPILER2_PRESENT(|| DoEscapeAnalysis)))
|
||||
{
|
||||
if (_barrier_active) sync_and_suspend_one();
|
||||
}
|
||||
|
||||
// Revert ea based optimizations for all java threads
|
||||
EscapeBarrier(bool barrier_active, JavaThread* calling_thread)
|
||||
: _calling_thread(calling_thread), _deoptee_thread(NULL),
|
||||
_barrier_active(barrier_active && (JVMCI_ONLY(UseJVMCICompiler) NOT_JVMCI(false)
|
||||
COMPILER2_PRESENT(|| DoEscapeAnalysis)))
|
||||
{
|
||||
if (_barrier_active) sync_and_suspend_all();
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
public:
|
||||
EscapeBarrier(bool barrier_active, JavaThread* calling_thread, JavaThread* deoptee_thread) { }
|
||||
EscapeBarrier(bool barrier_active, JavaThread* calling_thread) { }
|
||||
static bool deoptimizing_objects_for_all_threads() { return false; }
|
||||
bool barrier_active() const { return false; }
|
||||
#endif // COMPILER2_OR_JVMCI
|
||||
|
||||
// Deoptimize objects, i.e. reallocate and relock them. The target frames are deoptimized.
|
||||
// The methods return false iff at least one reallocation failed.
|
||||
bool deoptimize_objects(intptr_t* fr_id) {
|
||||
return true COMPILER2_OR_JVMCI_PRESENT(&& deoptimize_objects_internal(deoptee_thread(), fr_id));
|
||||
}
|
||||
|
||||
bool deoptimize_objects(int depth) NOT_COMPILER2_OR_JVMCI_RETURN_(true);
|
||||
|
||||
// Find and deoptimize non escaping objects and the holding frames on all stacks.
|
||||
bool deoptimize_objects_all_threads() NOT_COMPILER2_OR_JVMCI_RETURN_(true);
|
||||
|
||||
// A java thread was added to the list of threads.
|
||||
static void thread_added(JavaThread* jt) NOT_COMPILER2_OR_JVMCI_RETURN;
|
||||
|
||||
// A java thread was removed from the list of threads.
|
||||
static void thread_removed(JavaThread* jt) NOT_COMPILER2_OR_JVMCI_RETURN;
|
||||
|
||||
#if COMPILER2_OR_JVMCI
|
||||
// Returns true iff objects were reallocated and relocked because of access through JVMTI.
|
||||
static bool objs_are_deoptimized(JavaThread* thread, intptr_t* fr_id);
|
||||
|
||||
static bool deoptimizing_objects_for_all_threads() { return _deoptimizing_objects_for_all_threads; }
|
||||
|
||||
~EscapeBarrier() {
|
||||
if (!barrier_active()) return;
|
||||
if (all_threads()) {
|
||||
resume_all();
|
||||
} else {
|
||||
resume_one();
|
||||
}
|
||||
}
|
||||
|
||||
// Should revert optimizations for all threads.
|
||||
bool all_threads() const { return _deoptee_thread == NULL; }
|
||||
|
||||
// Current thread deoptimizes its own objects.
|
||||
bool self_deopt() const { return _calling_thread == _deoptee_thread; }
|
||||
|
||||
// Inactive barriers are created if no local objects can escape.
|
||||
bool barrier_active() const { return _barrier_active; }
|
||||
|
||||
// accessors
|
||||
JavaThread* calling_thread() const { return _calling_thread; }
|
||||
JavaThread* deoptee_thread() const { return _deoptee_thread; }
|
||||
#endif // COMPILER2_OR_JVMCI
|
||||
};
|
||||
|
||||
#endif // SHARE_RUNTIME_ESCAPEBARRIER_HPP
|
@ -391,6 +391,29 @@ const intx ObjectAlignmentInBytes = 8;
|
||||
notproduct(bool, WalkStackALot, false, \
|
||||
"Trace stack (no print) at every exit from the runtime system") \
|
||||
\
|
||||
develop(bool, DeoptimizeObjectsALot, false, \
|
||||
"For testing purposes concurrent threads revert optimizations " \
|
||||
"based on escape analysis at intervals given with " \
|
||||
"DeoptimizeObjectsALotInterval=n. The thread count is given " \
|
||||
"with DeoptimizeObjectsALotThreadCountSingle and " \
|
||||
"DeoptimizeObjectsALotThreadCountAll.") \
|
||||
\
|
||||
develop(uint64_t, DeoptimizeObjectsALotInterval, 5, \
|
||||
"Interval for DeoptimizeObjectsALot.") \
|
||||
range(0, max_jlong) \
|
||||
\
|
||||
develop(int, DeoptimizeObjectsALotThreadCountSingle, 1, \
|
||||
"The number of threads that revert optimizations based on " \
|
||||
"escape analysis for a single thread if DeoptimizeObjectsALot " \
|
||||
"is enabled. The target thread is selected round robin." ) \
|
||||
range(0, max_jint) \
|
||||
\
|
||||
develop(int, DeoptimizeObjectsALotThreadCountAll, 1, \
|
||||
"The number of threads that revert optimizations based on " \
|
||||
"escape analysis for all threads if DeoptimizeObjectsALot " \
|
||||
"is enabled." ) \
|
||||
range(0, max_jint) \
|
||||
\
|
||||
notproduct(bool, VerifyLastFrame, false, \
|
||||
"Verify oops on last frame on entry to VM") \
|
||||
\
|
||||
|
@ -52,6 +52,7 @@ Mutex* JmethodIdCreation_lock = NULL;
|
||||
Mutex* JfieldIdCreation_lock = NULL;
|
||||
Monitor* JNICritical_lock = NULL;
|
||||
Mutex* JvmtiThreadState_lock = NULL;
|
||||
Monitor* EscapeBarrier_lock = NULL;
|
||||
Monitor* Heap_lock = NULL;
|
||||
Mutex* ExpandHeap_lock = NULL;
|
||||
Mutex* AdapterHandlerLibrary_lock = NULL;
|
||||
@ -296,6 +297,7 @@ void mutex_init() {
|
||||
def(MultiArray_lock , PaddedMutex , nonleaf+2, false, _safepoint_check_always);
|
||||
|
||||
def(JvmtiThreadState_lock , PaddedMutex , nonleaf+2, false, _safepoint_check_always); // Used by JvmtiThreadState/JvmtiEventController
|
||||
def(EscapeBarrier_lock , PaddedMonitor, leaf, false, _safepoint_check_never); // Used to synchronize object reallocation/relocking triggered by JVMTI
|
||||
def(Management_lock , PaddedMutex , nonleaf+2, false, _safepoint_check_always); // used for JVM management
|
||||
|
||||
def(ConcurrentGCBreakpoints_lock , PaddedMonitor, nonleaf, true, _safepoint_check_always);
|
||||
|
@ -45,6 +45,7 @@ extern Mutex* JmethodIdCreation_lock; // a lock on creating JNI metho
|
||||
extern Mutex* JfieldIdCreation_lock; // a lock on creating JNI static field identifiers
|
||||
extern Monitor* JNICritical_lock; // a lock used while entering and exiting JNI critical regions, allows GC to sometimes get in
|
||||
extern Mutex* JvmtiThreadState_lock; // a lock on modification of JVMTI thread data
|
||||
extern Monitor* EscapeBarrier_lock; // a lock to sync reallocating and relocking objects because of JVMTI access
|
||||
extern Monitor* Heap_lock; // a lock on the heap
|
||||
extern Mutex* ExpandHeap_lock; // a lock on expanding the heap
|
||||
extern Mutex* AdapterHandlerLibrary_lock; // a lock on the AdapterHandlerLibrary
|
||||
|
@ -34,6 +34,7 @@
|
||||
#include "memory/resourceArea.hpp"
|
||||
#include "oops/markWord.hpp"
|
||||
#include "oops/oop.inline.hpp"
|
||||
#include "prims/jvmtiDeferredUpdates.hpp"
|
||||
#include "runtime/atomic.hpp"
|
||||
#include "runtime/handles.inline.hpp"
|
||||
#include "runtime/interfaceSupport.inline.hpp"
|
||||
@ -1560,7 +1561,8 @@ void ObjectMonitor::wait(jlong millis, bool interruptible, TRAPS) {
|
||||
jt->set_current_waiting_monitor(NULL);
|
||||
|
||||
guarantee(_recursions == 0, "invariant");
|
||||
_recursions = save; // restore the old recursion count
|
||||
_recursions = save // restore the old recursion count
|
||||
+ JvmtiDeferredUpdates::get_and_reset_relock_count_after_wait(jt); // increased by the deferred relock count
|
||||
_waiters--; // decrement the number of waiters
|
||||
|
||||
// Verify a few postconditions
|
||||
|
@ -989,6 +989,10 @@ void ThreadSafepointState::handle_polling_page_exception() {
|
||||
|
||||
// Process pending operation
|
||||
SafepointMechanism::process_if_requested(self);
|
||||
// We have to wait if we are here because of a handshake for object deoptimization.
|
||||
if (self->is_obj_deopt_suspend()) {
|
||||
self->wait_for_object_deoptimization();
|
||||
}
|
||||
self->check_and_handle_async_exceptions();
|
||||
|
||||
// restore oop result, if any
|
||||
@ -1006,6 +1010,10 @@ void ThreadSafepointState::handle_polling_page_exception() {
|
||||
|
||||
// Process pending operation
|
||||
SafepointMechanism::process_if_requested(self);
|
||||
// We have to wait if we are here because of a handshake for object deoptimization.
|
||||
if (self->is_obj_deopt_suspend()) {
|
||||
self->wait_for_object_deoptimization();
|
||||
}
|
||||
set_at_poll_safepoint(false);
|
||||
|
||||
// If we have a pending async exception deoptimize the frame
|
||||
|
@ -62,6 +62,7 @@
|
||||
#include "oops/typeArrayOop.inline.hpp"
|
||||
#include "oops/verifyOopClosure.hpp"
|
||||
#include "prims/jvm_misc.hpp"
|
||||
#include "prims/jvmtiDeferredUpdates.hpp"
|
||||
#include "prims/jvmtiExport.hpp"
|
||||
#include "prims/jvmtiThreadState.hpp"
|
||||
#include "runtime/arguments.hpp"
|
||||
@ -119,6 +120,7 @@
|
||||
#include "utilities/macros.hpp"
|
||||
#include "utilities/preserveException.hpp"
|
||||
#include "utilities/singleWriterSynchronizer.hpp"
|
||||
#include "utilities/spinYield.hpp"
|
||||
#include "utilities/vmError.hpp"
|
||||
#if INCLUDE_JVMCI
|
||||
#include "jvmci/jvmci.hpp"
|
||||
@ -1702,7 +1704,7 @@ JavaThread::JavaThread() :
|
||||
_deopt_nmethod(nullptr),
|
||||
_vframe_array_head(nullptr),
|
||||
_vframe_array_last(nullptr),
|
||||
_deferred_locals_updates(nullptr),
|
||||
_jvmti_deferred_updates(nullptr),
|
||||
_callee_target(nullptr),
|
||||
_vm_result(nullptr),
|
||||
_vm_result_2(nullptr),
|
||||
@ -1903,17 +1905,13 @@ JavaThread::~JavaThread() {
|
||||
delete old_array;
|
||||
}
|
||||
|
||||
GrowableArray<jvmtiDeferredLocalVariableSet*>* deferred = deferred_locals();
|
||||
if (deferred != NULL) {
|
||||
JvmtiDeferredUpdates* updates = deferred_updates();
|
||||
if (updates != NULL) {
|
||||
// This can only happen if thread is destroyed before deoptimization occurs.
|
||||
assert(deferred->length() != 0, "empty array!");
|
||||
do {
|
||||
jvmtiDeferredLocalVariableSet* dlv = deferred->at(0);
|
||||
deferred->remove_at(0);
|
||||
// individual jvmtiDeferredLocalVariableSet are CHeapObj's
|
||||
delete dlv;
|
||||
} while (deferred->length() != 0);
|
||||
delete deferred;
|
||||
assert(updates->count() > 0, "Updates holder not deleted");
|
||||
// free deferred updates.
|
||||
delete updates;
|
||||
set_deferred_updates(NULL);
|
||||
}
|
||||
|
||||
// All Java related clean up happens in exit
|
||||
@ -2405,6 +2403,11 @@ void JavaThread::handle_special_runtime_exit_condition(bool check_asyncs) {
|
||||
java_suspend_self_with_safepoint_check();
|
||||
}
|
||||
|
||||
if (is_obj_deopt_suspend()) {
|
||||
frame_anchor()->make_walkable(this);
|
||||
wait_for_object_deoptimization();
|
||||
}
|
||||
|
||||
// We might be here for reasons in addition to the self-suspend request
|
||||
// so check for other async requests.
|
||||
if (check_asyncs) {
|
||||
@ -2610,6 +2613,63 @@ void JavaThread::java_suspend_self_with_safepoint_check() {
|
||||
}
|
||||
}
|
||||
|
||||
// Wait for another thread to perform object reallocation and relocking on behalf of
|
||||
// this thread.
|
||||
// This method is very similar to JavaThread::java_suspend_self_with_safepoint_check()
|
||||
// and has the same callers. It also performs a raw thread state transition to
|
||||
// _thread_blocked and back again to the original state before returning. The current
|
||||
// thread is required to change to _thread_blocked in order to be seen to be
|
||||
// safepoint/handshake safe whilst suspended and only after becoming handshake safe,
|
||||
// the other thread can complete the handshake used to synchronize with this thread
|
||||
// and then perform the reallocation and relocking. We cannot use the thread state
|
||||
// transition helpers because we arrive here in various states and also because the
|
||||
// helpers indirectly call this method. After leaving _thread_blocked we have to
|
||||
// check for safepoint/handshake, except if _thread_in_native. The thread is safe
|
||||
// without blocking then. Allowed states are enumerated in
|
||||
// SafepointSynchronize::block(). See also EscapeBarrier::sync_and_suspend_*()
|
||||
|
||||
void JavaThread::wait_for_object_deoptimization() {
|
||||
assert(!has_last_Java_frame() || frame_anchor()->walkable(), "should have walkable stack");
|
||||
assert(this == Thread::current(), "invariant");
|
||||
JavaThreadState state = thread_state();
|
||||
|
||||
bool spin_wait = os::is_MP();
|
||||
do {
|
||||
set_thread_state(_thread_blocked);
|
||||
// Check if _external_suspend was set in the previous loop iteration.
|
||||
if (is_external_suspend()) {
|
||||
java_suspend_self();
|
||||
}
|
||||
// Wait for object deoptimization if requested.
|
||||
if (spin_wait) {
|
||||
// A single deoptimization is typically very short. Microbenchmarks
|
||||
// showed 5% better performance when spinning.
|
||||
const uint spin_limit = 10 * SpinYield::default_spin_limit;
|
||||
SpinYield spin(spin_limit);
|
||||
for (uint i = 0; is_obj_deopt_suspend() && i < spin_limit; i++) {
|
||||
spin.wait();
|
||||
}
|
||||
// Spin just once
|
||||
spin_wait = false;
|
||||
} else {
|
||||
MonitorLocker ml(this, EscapeBarrier_lock, Monitor::_no_safepoint_check_flag);
|
||||
if (is_obj_deopt_suspend()) {
|
||||
ml.wait();
|
||||
}
|
||||
}
|
||||
// The current thread could have been suspended again. We have to check for
|
||||
// suspend after restoring the saved state. Without this the current thread
|
||||
// might return to _thread_in_Java and execute bytecode.
|
||||
set_thread_state_fence(state);
|
||||
|
||||
if (state != _thread_in_native) {
|
||||
SafepointMechanism::process_if_requested(this);
|
||||
}
|
||||
// A handshake for obj. deoptimization suspend could have been processed so
|
||||
// we must check after processing.
|
||||
} while (is_obj_deopt_suspend() || is_external_suspend());
|
||||
}
|
||||
|
||||
#ifdef ASSERT
|
||||
// Verify the JavaThread has not yet been published in the Threads::list, and
|
||||
// hence doesn't need protection from concurrent access at this stage.
|
||||
@ -2639,6 +2699,10 @@ void JavaThread::check_safepoint_and_suspend_for_native_trans(JavaThread *thread
|
||||
SafepointMechanism::process_if_requested(thread);
|
||||
}
|
||||
|
||||
if (thread->is_obj_deopt_suspend()) {
|
||||
thread->wait_for_object_deoptimization();
|
||||
}
|
||||
|
||||
JFR_ONLY(SUSPEND_THREAD_CONDITIONAL(thread);)
|
||||
}
|
||||
|
||||
@ -2802,7 +2866,7 @@ void JavaThread::oops_do_no_frames(OopClosure* f, CodeBlobClosure* cf) {
|
||||
assert(vframe_array_head() == NULL, "deopt in progress at a safepoint!");
|
||||
// If we have deferred set_locals there might be oops waiting to be
|
||||
// written
|
||||
GrowableArray<jvmtiDeferredLocalVariableSet*>* list = deferred_locals();
|
||||
GrowableArray<jvmtiDeferredLocalVariableSet*>* list = JvmtiDeferredUpdates::deferred_locals(this);
|
||||
if (list != NULL) {
|
||||
for (int i = 0; i < list->length(); i++) {
|
||||
list->at(i)->oops_do(f);
|
||||
@ -4384,6 +4448,9 @@ void Threads::add(JavaThread* p, bool force_daemon) {
|
||||
|
||||
// Possible GC point.
|
||||
Events::log(p, "Thread added: " INTPTR_FORMAT, p2i(p));
|
||||
|
||||
// Make new thread known to active EscapeBarrier
|
||||
EscapeBarrier::thread_added(p);
|
||||
}
|
||||
|
||||
void Threads::remove(JavaThread* p, bool is_daemon) {
|
||||
@ -4424,6 +4491,9 @@ void Threads::remove(JavaThread* p, bool is_daemon) {
|
||||
// to do callbacks into the safepoint code. However, the safepoint code is not aware
|
||||
// of this thread since it is removed from the queue.
|
||||
p->set_terminated_value();
|
||||
|
||||
// Notify threads waiting in EscapeBarriers
|
||||
EscapeBarrier::thread_removed(p);
|
||||
} // unlock Threads_lock
|
||||
|
||||
// Since Events::log uses a lock, we grab it outside the Threads_lock
|
||||
|
@ -83,7 +83,7 @@ class vframe;
|
||||
class javaVFrame;
|
||||
|
||||
class DeoptResourceMark;
|
||||
class jvmtiDeferredLocalVariableSet;
|
||||
class JvmtiDeferredUpdates;
|
||||
|
||||
class ThreadClosure;
|
||||
class ICRefillVerifier;
|
||||
@ -303,7 +303,8 @@ class Thread: public ThreadShadow {
|
||||
_has_async_exception = 0x00000001U, // there is a pending async exception
|
||||
_critical_native_unlock = 0x00000002U, // Must call back to unlock JNI critical lock
|
||||
|
||||
_trace_flag = 0x00000004U // call tracing backend
|
||||
_trace_flag = 0x00000004U, // call tracing backend
|
||||
_obj_deopt = 0x00000008U // suspend for object reallocation and relocking for JVMTI agent
|
||||
};
|
||||
|
||||
// various suspension related flags - atomically updated
|
||||
@ -550,6 +551,9 @@ class Thread: public ThreadShadow {
|
||||
inline void set_trace_flag();
|
||||
inline void clear_trace_flag();
|
||||
|
||||
inline void set_obj_deopt_flag();
|
||||
inline void clear_obj_deopt_flag();
|
||||
|
||||
// Support for Unhandled Oop detection
|
||||
// Add the field for both, fastdebug and debug, builds to keep
|
||||
// Thread's fields layout the same.
|
||||
@ -624,6 +628,8 @@ class Thread: public ThreadShadow {
|
||||
|
||||
bool is_trace_suspend() { return (_suspend_flags & _trace_flag) != 0; }
|
||||
|
||||
bool is_obj_deopt_suspend() { return (_suspend_flags & _obj_deopt) != 0; }
|
||||
|
||||
// For tracking the heavyweight monitor the thread is pending on.
|
||||
ObjectMonitor* current_pending_monitor() {
|
||||
return _current_pending_monitor;
|
||||
@ -1059,11 +1065,10 @@ class JavaThread: public Thread {
|
||||
CompiledMethod* _deopt_nmethod; // CompiledMethod that is currently being deoptimized
|
||||
vframeArray* _vframe_array_head; // Holds the heap of the active vframeArrays
|
||||
vframeArray* _vframe_array_last; // Holds last vFrameArray we popped
|
||||
// Because deoptimization is lazy we must save jvmti requests to set locals
|
||||
// in compiled frames until we deoptimize and we have an interpreter frame.
|
||||
// This holds the pointer to array (yeah like there might be more than one) of
|
||||
// description of compiled vframes that have locals that need to be updated.
|
||||
GrowableArray<jvmtiDeferredLocalVariableSet*>* _deferred_locals_updates;
|
||||
// Holds updates by JVMTI agents for compiled frames that cannot be performed immediately. They
|
||||
// will be carried out as soon as possible which, in most cases, is just before deoptimization of
|
||||
// the frame, when control returns to it.
|
||||
JvmtiDeferredUpdates* _jvmti_deferred_updates;
|
||||
|
||||
// Handshake value for fixing 6243940. We need a place for the i2c
|
||||
// adapter to store the callee Method*. This value is NEVER live
|
||||
@ -1356,6 +1361,10 @@ class JavaThread: public Thread {
|
||||
void java_resume(); // higher-level resume logic called by the public APIs
|
||||
int java_suspend_self(); // low-level self-suspension mechanics
|
||||
|
||||
// Synchronize with another thread that is deoptimizing objects of the
|
||||
// current thread, i.e. reverts optimizations based on escape analysis.
|
||||
void wait_for_object_deoptimization();
|
||||
|
||||
private:
|
||||
// mid-level wrapper around java_suspend_self to set up correct state and
|
||||
// check for a pending safepoint at the end
|
||||
@ -1415,7 +1424,7 @@ class JavaThread: public Thread {
|
||||
// Whenever a thread transitions from native to vm/java it must suspend
|
||||
// if external|deopt suspend is present.
|
||||
bool is_suspend_after_native() const {
|
||||
return (_suspend_flags & (_external_suspend JFR_ONLY(| _trace_flag))) != 0;
|
||||
return (_suspend_flags & (_external_suspend | _obj_deopt JFR_ONLY(| _trace_flag))) != 0;
|
||||
}
|
||||
|
||||
// external suspend request is completed
|
||||
@ -1488,7 +1497,7 @@ class JavaThread: public Thread {
|
||||
// we have checked is_external_suspend(), we will recheck its value
|
||||
// under SR_lock in java_suspend_self().
|
||||
return (_special_runtime_exit_condition != _no_async_condition) ||
|
||||
is_external_suspend() || is_trace_suspend();
|
||||
is_external_suspend() || is_trace_suspend() || is_obj_deopt_suspend();
|
||||
}
|
||||
|
||||
void set_pending_unsafe_access_error() { _special_runtime_exit_condition = _async_unsafe_access_error; }
|
||||
@ -1505,8 +1514,8 @@ class JavaThread: public Thread {
|
||||
vframeArray* vframe_array_head() const { return _vframe_array_head; }
|
||||
|
||||
// Side structure for deferring update of java frame locals until deopt occurs
|
||||
GrowableArray<jvmtiDeferredLocalVariableSet*>* deferred_locals() const { return _deferred_locals_updates; }
|
||||
void set_deferred_locals(GrowableArray<jvmtiDeferredLocalVariableSet *>* vf) { _deferred_locals_updates = vf; }
|
||||
JvmtiDeferredUpdates* deferred_updates() const { return _jvmti_deferred_updates; }
|
||||
void set_deferred_updates(JvmtiDeferredUpdates* du) { _jvmti_deferred_updates = du; }
|
||||
|
||||
// These only really exist to make debugging deopt problems simpler
|
||||
|
||||
|
@ -65,6 +65,12 @@ inline void Thread::set_trace_flag() {
|
||||
inline void Thread::clear_trace_flag() {
|
||||
clear_suspend_flag(_trace_flag);
|
||||
}
|
||||
inline void Thread::set_obj_deopt_flag() {
|
||||
set_suspend_flag(_obj_deopt);
|
||||
}
|
||||
inline void Thread::clear_obj_deopt_flag() {
|
||||
clear_suspend_flag(_obj_deopt);
|
||||
}
|
||||
|
||||
inline jlong Thread::cooked_allocated_bytes() {
|
||||
jlong allocated_bytes = Atomic::load_acquire(&_allocated_bytes);
|
||||
|
@ -82,6 +82,11 @@ vframe* vframe::new_vframe(const frame* f, const RegisterMap* reg_map, JavaThrea
|
||||
}
|
||||
}
|
||||
|
||||
// Entry frame
|
||||
if (f->is_entry_frame()) {
|
||||
return new entryVFrame(f, reg_map, thread);
|
||||
}
|
||||
|
||||
// External frame
|
||||
return new externalVFrame(f, reg_map, thread);
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 1997, 2018, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 1997, 2020, 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
|
||||
@ -33,6 +33,7 @@
|
||||
#include "interpreter/oopMapCache.hpp"
|
||||
#include "oops/instanceKlass.hpp"
|
||||
#include "oops/oop.inline.hpp"
|
||||
#include "prims/jvmtiDeferredUpdates.hpp"
|
||||
#include "runtime/basicLock.hpp"
|
||||
#include "runtime/frame.inline.hpp"
|
||||
#include "runtime/handles.inline.hpp"
|
||||
@ -64,7 +65,7 @@ StackValueCollection* compiledVFrame::locals() const {
|
||||
|
||||
// Replace the original values with any stores that have been
|
||||
// performed through compiledVFrame::update_locals.
|
||||
GrowableArray<jvmtiDeferredLocalVariableSet*>* list = thread()->deferred_locals();
|
||||
GrowableArray<jvmtiDeferredLocalVariableSet*>* list = JvmtiDeferredUpdates::deferred_locals(thread());
|
||||
if (list != NULL ) {
|
||||
// In real life this never happens or is typically a single element search
|
||||
for (int i = 0; i < list->length(); i++) {
|
||||
@ -103,7 +104,7 @@ void compiledVFrame::update_monitor(int index, MonitorInfo* val) {
|
||||
|
||||
void compiledVFrame::update_deferred_value(BasicType type, int index, jvalue value) {
|
||||
assert(fr().is_deoptimized_frame(), "frame must be scheduled for deoptimization");
|
||||
GrowableArray<jvmtiDeferredLocalVariableSet*>* deferred = thread()->deferred_locals();
|
||||
GrowableArray<jvmtiDeferredLocalVariableSet*>* deferred = JvmtiDeferredUpdates::deferred_locals(thread());
|
||||
jvmtiDeferredLocalVariableSet* locals = NULL;
|
||||
if (deferred != NULL ) {
|
||||
// See if this vframe has already had locals with deferred writes
|
||||
@ -117,8 +118,8 @@ void compiledVFrame::update_deferred_value(BasicType type, int index, jvalue val
|
||||
} else {
|
||||
// No deferred updates pending for this thread.
|
||||
// allocate in C heap
|
||||
deferred = new(ResourceObj::C_HEAP, mtCompiler) GrowableArray<jvmtiDeferredLocalVariableSet*> (1, mtCompiler);
|
||||
thread()->set_deferred_locals(deferred);
|
||||
JvmtiDeferredUpdates::create_for(thread());
|
||||
deferred = JvmtiDeferredUpdates::deferred_locals(thread());
|
||||
}
|
||||
if (locals == NULL) {
|
||||
locals = new jvmtiDeferredLocalVariableSet(method(), bci(), fr().id(), vframe_id());
|
||||
@ -128,6 +129,56 @@ void compiledVFrame::update_deferred_value(BasicType type, int index, jvalue val
|
||||
locals->set_value_at(index, type, value);
|
||||
}
|
||||
|
||||
// After object deoptimization, that is object reallocation and relocking, we
|
||||
// create deferred updates for all objects in scope. No new update will be
|
||||
// created if a deferred update already exists. It is not easy to see how this
|
||||
// is achieved: the deoptimized objects are in the arrays returned by locals(),
|
||||
// expressions(), and monitors(). For each object in these arrays we create a
|
||||
// deferred updated. If an update already exists, then it will override the
|
||||
// corresponding deoptimized object returned in one of the arrays. So the
|
||||
// original update is kept.
|
||||
void compiledVFrame::create_deferred_updates_after_object_deoptimization() {
|
||||
// locals
|
||||
GrowableArray<ScopeValue*>* scopeLocals = scope()->locals();
|
||||
StackValueCollection* lcls = locals();
|
||||
if (lcls != NULL) {
|
||||
for (int i2 = 0; i2 < lcls->size(); i2++) {
|
||||
StackValue* var = lcls->at(i2);
|
||||
if (var->type() == T_OBJECT && scopeLocals->at(i2)->is_object()) {
|
||||
jvalue val;
|
||||
val.l = cast_from_oop<jobject>(lcls->at(i2)->get_obj()());
|
||||
update_local(T_OBJECT, i2, val);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// expressions
|
||||
GrowableArray<ScopeValue*>* scopeExpressions = scope()->expressions();
|
||||
StackValueCollection* exprs = expressions();
|
||||
if (exprs != NULL) {
|
||||
for (int i2 = 0; i2 < exprs->size(); i2++) {
|
||||
StackValue* var = exprs->at(i2);
|
||||
if (var->type() == T_OBJECT && scopeExpressions->at(i2)->is_object()) {
|
||||
jvalue val;
|
||||
val.l = cast_from_oop<jobject>(exprs->at(i2)->get_obj()());
|
||||
update_stack(T_OBJECT, i2, val);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// monitors
|
||||
GrowableArray<MonitorInfo*>* mtrs = monitors();
|
||||
if (mtrs != NULL) {
|
||||
for (int i2 = 0; i2 < mtrs->length(); i2++) {
|
||||
if (mtrs->at(i2)->eliminated()) {
|
||||
assert(!mtrs->at(i2)->owner_is_scalar_replaced(),
|
||||
"reallocation failure, should not update");
|
||||
update_monitor(i2, mtrs->at(i2));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StackValueCollection* compiledVFrame::expressions() const {
|
||||
// Natives has no scope
|
||||
if (scope() == NULL) return new StackValueCollection(0);
|
||||
@ -144,7 +195,7 @@ StackValueCollection* compiledVFrame::expressions() const {
|
||||
|
||||
// Replace the original values with any stores that have been
|
||||
// performed through compiledVFrame::update_stack.
|
||||
GrowableArray<jvmtiDeferredLocalVariableSet*>* list = thread()->deferred_locals();
|
||||
GrowableArray<jvmtiDeferredLocalVariableSet*>* list = JvmtiDeferredUpdates::deferred_locals(thread());
|
||||
if (list != NULL ) {
|
||||
// In real life this never happens or is typically a single element search
|
||||
for (int i = 0; i < list->length(); i++) {
|
||||
@ -218,7 +269,7 @@ GrowableArray<MonitorInfo*>* compiledVFrame::monitors() const {
|
||||
|
||||
// Replace the original values with any stores that have been
|
||||
// performed through compiledVFrame::update_monitors.
|
||||
GrowableArray<jvmtiDeferredLocalVariableSet*>* list = thread()->deferred_locals();
|
||||
GrowableArray<jvmtiDeferredLocalVariableSet*>* list = JvmtiDeferredUpdates::deferred_locals(thread());
|
||||
if (list != NULL ) {
|
||||
// In real life this never happens or is typically a single element search
|
||||
for (int i = 0; i < list->length(); i++) {
|
||||
@ -309,6 +360,24 @@ bool compiledVFrame::should_reexecute() const {
|
||||
return scope()->should_reexecute();
|
||||
}
|
||||
|
||||
bool compiledVFrame::has_ea_local_in_scope() const {
|
||||
if (scope() == NULL) {
|
||||
// native nmethod, all objs escape
|
||||
assert(code()->as_nmethod()->is_native_method(), "must be native");
|
||||
return false;
|
||||
}
|
||||
return (scope()->objects() != NULL) || scope()->has_ea_local_in_scope();
|
||||
}
|
||||
|
||||
bool compiledVFrame::arg_escape() const {
|
||||
if (scope() == NULL) {
|
||||
// native nmethod, all objs escape
|
||||
assert(code()->as_nmethod()->is_native_method(), "must be native");
|
||||
return false;
|
||||
}
|
||||
return scope()->arg_escape();
|
||||
}
|
||||
|
||||
vframe* compiledVFrame::sender() const {
|
||||
const frame f = fr();
|
||||
if (scope() == NULL) {
|
||||
@ -330,6 +399,7 @@ jvmtiDeferredLocalVariableSet::jvmtiDeferredLocalVariableSet(Method* method, int
|
||||
_vframe_id = vframe_id;
|
||||
// Alway will need at least one, must be on C heap
|
||||
_locals = new(ResourceObj::C_HEAP, mtCompiler) GrowableArray<jvmtiDeferredLocalVariable*> (1, mtCompiler);
|
||||
_objects_are_deoptimized = false;
|
||||
}
|
||||
|
||||
jvmtiDeferredLocalVariableSet::~jvmtiDeferredLocalVariableSet() {
|
||||
@ -424,7 +494,11 @@ void jvmtiDeferredLocalVariableSet::update_monitors(GrowableArray<MonitorInfo*>*
|
||||
if (val->index() >= method()->max_locals() + method()->max_stack()) {
|
||||
int lock_index = val->index() - (method()->max_locals() + method()->max_stack());
|
||||
MonitorInfo* info = monitors->at(lock_index);
|
||||
MonitorInfo* new_info = new MonitorInfo((oopDesc*)val->value().l, info->lock(), info->eliminated(), info->owner_is_scalar_replaced());
|
||||
// Originally the owner may have been scalar replaced but as an update
|
||||
// exists it must have been deoptimized, i.e. reallocated to the heap, and
|
||||
// now it is considered not to be scalar replaced.
|
||||
MonitorInfo* new_info = new MonitorInfo((oopDesc*)val->value().l, info->lock(),
|
||||
info->eliminated(), false);
|
||||
monitors->at_put(lock_index, new_info);
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 1997, 2019, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 1997, 2020, 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
|
||||
@ -28,6 +28,9 @@
|
||||
#include "runtime/vframe.hpp"
|
||||
|
||||
class compiledVFrame: public javaVFrame {
|
||||
|
||||
friend class EscapeBarrier;
|
||||
|
||||
public:
|
||||
// JVM state
|
||||
Method* method() const;
|
||||
@ -37,6 +40,8 @@ class compiledVFrame: public javaVFrame {
|
||||
StackValueCollection* expressions() const;
|
||||
GrowableArray<MonitorInfo*>* monitors() const;
|
||||
int vframe_id() const { return _vframe_id; }
|
||||
bool has_ea_local_in_scope() const;
|
||||
bool arg_escape() const; // at call with arg escape in parameter list
|
||||
|
||||
void set_locals(StackValueCollection* values) const;
|
||||
|
||||
@ -53,6 +58,11 @@ class compiledVFrame: public javaVFrame {
|
||||
|
||||
void update_deferred_value(BasicType type, int index, jvalue value);
|
||||
|
||||
// After object deoptimization, that is object reallocation and relocking, we
|
||||
// create deferred updates for all objects in scope. No new update will be
|
||||
// created if a deferred update already exists.
|
||||
void create_deferred_updates_after_object_deoptimization();
|
||||
|
||||
public:
|
||||
// Constructors
|
||||
compiledVFrame(const frame* fr, const RegisterMap* reg_map, JavaThread* thread, CompiledMethod* nm);
|
||||
@ -95,71 +105,4 @@ class compiledVFrame: public javaVFrame {
|
||||
#endif
|
||||
};
|
||||
|
||||
// In order to implement set_locals for compiled vframes we must
|
||||
// store updated locals in a data structure that contains enough
|
||||
// information to recognize equality with a vframe and to store
|
||||
// any updated locals.
|
||||
|
||||
class jvmtiDeferredLocalVariable;
|
||||
class jvmtiDeferredLocalVariableSet : public CHeapObj<mtCompiler> {
|
||||
friend class compiledVFrame;
|
||||
|
||||
private:
|
||||
|
||||
Method* _method;
|
||||
int _bci;
|
||||
intptr_t* _id;
|
||||
int _vframe_id;
|
||||
GrowableArray<jvmtiDeferredLocalVariable*>* _locals;
|
||||
|
||||
void update_value(StackValueCollection* locals, BasicType type, int index, jvalue value);
|
||||
|
||||
void set_value_at(int idx, BasicType typ, jvalue val);
|
||||
|
||||
public:
|
||||
// JVM state
|
||||
Method* method() const { return _method; }
|
||||
int bci() const { return _bci; }
|
||||
intptr_t* id() const { return _id; }
|
||||
int vframe_id() const { return _vframe_id; }
|
||||
|
||||
void update_locals(StackValueCollection* locals);
|
||||
void update_stack(StackValueCollection* locals);
|
||||
void update_monitors(GrowableArray<MonitorInfo*>* monitors);
|
||||
|
||||
// Does the vframe match this jvmtiDeferredLocalVariableSet
|
||||
bool matches(const vframe* vf);
|
||||
// GC
|
||||
void oops_do(OopClosure* f);
|
||||
|
||||
// constructor
|
||||
jvmtiDeferredLocalVariableSet(Method* method, int bci, intptr_t* id, int vframe_id);
|
||||
|
||||
// destructor
|
||||
~jvmtiDeferredLocalVariableSet();
|
||||
|
||||
|
||||
};
|
||||
|
||||
class jvmtiDeferredLocalVariable : public CHeapObj<mtCompiler> {
|
||||
public:
|
||||
|
||||
jvmtiDeferredLocalVariable(int index, BasicType type, jvalue value);
|
||||
|
||||
BasicType type(void) { return _type; }
|
||||
int index(void) { return _index; }
|
||||
jvalue value(void) { return _value; }
|
||||
// Only mutator is for value as only it can change
|
||||
void set_value(jvalue value) { _value = value; }
|
||||
// For gc
|
||||
oop* oop_addr(void) { return (oop*) &_value.l; }
|
||||
|
||||
private:
|
||||
|
||||
BasicType _type;
|
||||
jvalue _value;
|
||||
int _index;
|
||||
|
||||
};
|
||||
|
||||
#endif // SHARE_RUNTIME_VFRAME_HP_HPP
|
||||
|
@ -327,10 +327,14 @@
|
||||
#define COMPILER2_OR_JVMCI 1
|
||||
#define COMPILER2_OR_JVMCI_PRESENT(code) code
|
||||
#define NOT_COMPILER2_OR_JVMCI(code)
|
||||
#define NOT_COMPILER2_OR_JVMCI_RETURN /* next token must be ; */
|
||||
#define NOT_COMPILER2_OR_JVMCI_RETURN_(code) /* next token must be ; */
|
||||
#else
|
||||
#define COMPILER2_OR_JVMCI 0
|
||||
#define COMPILER2_OR_JVMCI_PRESENT(code)
|
||||
#define NOT_COMPILER2_OR_JVMCI(code) code
|
||||
#define NOT_COMPILER2_OR_JVMCI_RETURN {}
|
||||
#define NOT_COMPILER2_OR_JVMCI_RETURN_(code) { return code; }
|
||||
#endif
|
||||
|
||||
#ifdef TIERED
|
||||
|
@ -0,0 +1,610 @@
|
||||
/*
|
||||
* Copyright (c) 2020 SAP SE. 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @bug 8230956
|
||||
* @summary JVMTI agents can obtain references to not escaping objects using JVMTI Heap functions.
|
||||
* Therefore optimizations based on escape analysis have to be reverted,
|
||||
* i.e. scalar replaced objects need to be reallocated on the heap and objects with eliminated locking
|
||||
* need to be relocked.
|
||||
* @requires ((vm.compMode == "Xmixed") & vm.compiler2.enabled & vm.jvmti)
|
||||
* @library /test/lib /test/hotspot/jtreg
|
||||
* @build sun.hotspot.WhiteBox
|
||||
* @run driver ClassFileInstaller sun.hotspot.WhiteBox
|
||||
* @compile IterateHeapWithEscapeAnalysisEnabled.java
|
||||
*
|
||||
* @comment BLOCK BEGIN EXCLUSIVE TESTCASES {
|
||||
*
|
||||
* The following test cases are executed in fresh VMs because they require that the
|
||||
* capability can_tag_objects is not taken until dontinline_testMethod is jit compiled and
|
||||
* an activation of the compiled version is on stack of the target thread.
|
||||
*
|
||||
* Without JDK-8227745 these test cases require that escape analysis is disabled at
|
||||
* start-up because can_tag_objects can be taken lazily, potentially after loading an
|
||||
* agent dynamically by means of the attach API. Disabling escape analysis and invalidating
|
||||
* compiled methods does not help then because there may be compiled frames with ea-based
|
||||
* optimizations on stack. Just like in this collection of test cases.
|
||||
*
|
||||
* @run main/othervm/native
|
||||
* -agentlib:IterateHeapWithEscapeAnalysisEnabled
|
||||
* -XX:+UnlockDiagnosticVMOptions
|
||||
* -Xms256m -Xmx256m
|
||||
* -XX:+PrintCompilation -XX:+PrintInlining
|
||||
* -XX:+WhiteBoxAPI -Xbootclasspath/a:.
|
||||
* -Xbatch
|
||||
* -XX:CompileCommand=dontinline,*::dontinline_*
|
||||
* -XX:+DoEscapeAnalysis
|
||||
* IterateHeapWithEscapeAnalysisEnabled IterateOverReachableObjects
|
||||
* @run main/othervm/native
|
||||
* -agentlib:IterateHeapWithEscapeAnalysisEnabled
|
||||
* -XX:+UnlockDiagnosticVMOptions
|
||||
* -Xms256m -Xmx256m
|
||||
* -XX:+PrintCompilation -XX:+PrintInlining
|
||||
* -XX:+WhiteBoxAPI -Xbootclasspath/a:.
|
||||
* -Xbatch
|
||||
* -XX:CompileCommand=dontinline,*::dontinline_*
|
||||
* -XX:+DoEscapeAnalysis
|
||||
* IterateHeapWithEscapeAnalysisEnabled IterateOverHeap
|
||||
* @run main/othervm/native
|
||||
* -agentlib:IterateHeapWithEscapeAnalysisEnabled
|
||||
* -XX:+UnlockDiagnosticVMOptions
|
||||
* -Xms256m -Xmx256m
|
||||
* -XX:+PrintCompilation -XX:+PrintInlining
|
||||
* -XX:+WhiteBoxAPI -Xbootclasspath/a:.
|
||||
* -Xbatch
|
||||
* -XX:CompileCommand=dontinline,*::dontinline_*
|
||||
* -XX:+DoEscapeAnalysis
|
||||
* IterateHeapWithEscapeAnalysisEnabled IterateOverInstancesOfClass
|
||||
* @run main/othervm/native
|
||||
* -agentlib:IterateHeapWithEscapeAnalysisEnabled
|
||||
* -XX:+UnlockDiagnosticVMOptions
|
||||
* -Xms256m -Xmx256m
|
||||
* -XX:+PrintCompilation -XX:+PrintInlining
|
||||
* -XX:+WhiteBoxAPI -Xbootclasspath/a:.
|
||||
* -Xbatch
|
||||
* -XX:CompileCommand=dontinline,*::dontinline_*
|
||||
* -XX:+DoEscapeAnalysis
|
||||
* IterateHeapWithEscapeAnalysisEnabled FollowReferences
|
||||
* @run main/othervm/native
|
||||
* -agentlib:IterateHeapWithEscapeAnalysisEnabled
|
||||
* -XX:+UnlockDiagnosticVMOptions
|
||||
* -Xms256m -Xmx256m
|
||||
* -XX:+PrintCompilation -XX:+PrintInlining
|
||||
* -XX:+WhiteBoxAPI -Xbootclasspath/a:.
|
||||
* -Xbatch
|
||||
* -XX:CompileCommand=dontinline,*::dontinline_*
|
||||
* -XX:+DoEscapeAnalysis
|
||||
* IterateHeapWithEscapeAnalysisEnabled IterateThroughHeap
|
||||
*
|
||||
* @comment } BLOCK END EXCLUSIVE TESTCASES
|
||||
*
|
||||
* @comment BLOCK BEGIN NON EXCLUSIVE TESTCASES {
|
||||
*
|
||||
* @run main/othervm/native
|
||||
* -agentlib:IterateHeapWithEscapeAnalysisEnabled
|
||||
* -XX:+UnlockDiagnosticVMOptions
|
||||
* -Xms256m -Xmx256m
|
||||
* -XX:CompileCommand=dontinline,*::dontinline_*
|
||||
* -XX:+PrintCompilation
|
||||
* -XX:+PrintInlining
|
||||
* -XX:+WhiteBoxAPI -Xbootclasspath/a:.
|
||||
* -Xbatch
|
||||
* -XX:+DoEscapeAnalysis -XX:+EliminateAllocations -XX:+EliminateLocks -XX:+EliminateNestedLocks -XX:+UseBiasedLocking
|
||||
* IterateHeapWithEscapeAnalysisEnabled
|
||||
* @run main/othervm/native
|
||||
* -agentlib:IterateHeapWithEscapeAnalysisEnabled
|
||||
* -XX:+UnlockDiagnosticVMOptions
|
||||
* -Xms256m -Xmx256m
|
||||
* -XX:CompileCommand=dontinline,*::dontinline_*
|
||||
* -XX:+PrintCompilation
|
||||
* -XX:+PrintInlining
|
||||
* -XX:+WhiteBoxAPI -Xbootclasspath/a:.
|
||||
* -Xbatch
|
||||
* -XX:+DoEscapeAnalysis -XX:-EliminateAllocations -XX:+EliminateLocks -XX:+EliminateNestedLocks -XX:+UseBiasedLocking
|
||||
* IterateHeapWithEscapeAnalysisEnabled
|
||||
* @run main/othervm/native
|
||||
* -agentlib:IterateHeapWithEscapeAnalysisEnabled
|
||||
* -XX:+UnlockDiagnosticVMOptions
|
||||
* -Xms256m -Xmx256m
|
||||
* -XX:CompileCommand=dontinline,*::dontinline_*
|
||||
* -XX:+PrintCompilation
|
||||
* -XX:+PrintInlining
|
||||
* -XX:+WhiteBoxAPI -Xbootclasspath/a:.
|
||||
* -Xbatch
|
||||
* -XX:-DoEscapeAnalysis -XX:-EliminateAllocations -XX:+EliminateLocks -XX:+EliminateNestedLocks -XX:+UseBiasedLocking
|
||||
* IterateHeapWithEscapeAnalysisEnabled
|
||||
*
|
||||
* @comment } BLOCK END NON EXCLUSIVE TESTCASES
|
||||
*/
|
||||
|
||||
import compiler.whitebox.CompilerWhiteBoxTest;
|
||||
import jdk.test.lib.Asserts;
|
||||
import sun.hotspot.WhiteBox;
|
||||
|
||||
public class IterateHeapWithEscapeAnalysisEnabled {
|
||||
|
||||
public static final WhiteBox WB = WhiteBox.getWhiteBox();
|
||||
|
||||
public static final int COMPILE_THRESHOLD = CompilerWhiteBoxTest.THRESHOLD;
|
||||
|
||||
public static native int jvmtiTagClass(Class<?> cls, long tag);
|
||||
|
||||
// Methods to tag or count instances of a given class available in JVMTI
|
||||
public static enum TaggingAndCountingMethods {
|
||||
IterateOverReachableObjects,
|
||||
IterateOverHeap,
|
||||
IterateOverInstancesOfClass,
|
||||
FollowReferences,
|
||||
IterateThroughHeap
|
||||
}
|
||||
|
||||
public static native int acquireCanTagObjectsCapability();
|
||||
public static native int registerMethod(TaggingAndCountingMethods m, String name);
|
||||
public static native void agentTearDown();
|
||||
|
||||
/**
|
||||
* Count and tag instances of a given class.
|
||||
* @param cls Used by the method {@link TaggingAndCountingMethods#IterateOverInstancesOfClass} as class to count and tag instances of.
|
||||
* Ignored by other counting methods.
|
||||
* @param clsTag Tag of the class to count and tag instances of. Used by all methods except
|
||||
* {@link TaggingAndCountingMethods#IterateOverInstancesOfClass}
|
||||
* @param instanceTag The tag to be set for selected instances.
|
||||
* @param method JVMTI counting and tagging method to be used.
|
||||
* @return The number of instances or -1 if the call fails.
|
||||
*/
|
||||
public static native int countAndTagInstancesOfClass(Class<?> cls, long clsTag, long instanceTag, TaggingAndCountingMethods method);
|
||||
|
||||
/**
|
||||
* Get all objects tagged with the given tag.
|
||||
* @param tag The tag used to select objects.
|
||||
* @param result Selected objects are copied into this array.
|
||||
* @return -1 to indicated failure and 0 for success.
|
||||
*/
|
||||
public static native int getObjectsWithTag(long tag, Object[] result);
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
try {
|
||||
new IterateHeapWithEscapeAnalysisEnabled().runTestCases(args);
|
||||
} finally {
|
||||
agentTearDown();
|
||||
}
|
||||
}
|
||||
|
||||
public void runTestCases(String[] args) throws Exception {
|
||||
// register various instance tagging and counting methods with agent
|
||||
for (TaggingAndCountingMethods m : TaggingAndCountingMethods.values()) {
|
||||
msg("register instance count method " + m.name());
|
||||
int rc = registerMethod(m, m.name());
|
||||
Asserts.assertGreaterThanOrEqual(rc, 0, "method " + m.name() + " is unknown to agent");
|
||||
}
|
||||
|
||||
if (args.length > 0) {
|
||||
// EXCLUSIVE TEST CASES
|
||||
// cant_tag_objects is acquired after warmup. Use given tagging/counting method.
|
||||
new TestCase01(true, 100, TaggingAndCountingMethods.valueOf(args[0])).run();
|
||||
} else {
|
||||
// NON-EXCLUSIVE TEST CASES
|
||||
// cant_tag_objects is acquired before test cases are run but still during live phase.
|
||||
msgHL("Acquire capability can_tag_objects before first test case.");
|
||||
int err = acquireCanTagObjectsCapability();
|
||||
Asserts.assertEQ(0, err, "acquireCanTagObjectsCapability FAILED");
|
||||
|
||||
// run test cases
|
||||
for (TaggingAndCountingMethods m : TaggingAndCountingMethods.values()) {
|
||||
new TestCase01(false, 200, m).run();
|
||||
}
|
||||
new TestCase02a(200).run();
|
||||
new TestCase02b(300).run();
|
||||
}
|
||||
}
|
||||
|
||||
static class ABBox {
|
||||
public int aVal;
|
||||
public int bVal;
|
||||
public TestCaseBase testCase;
|
||||
|
||||
public ABBox() { /* empty */ }
|
||||
|
||||
public ABBox(TestCaseBase testCase) {
|
||||
this.testCase = testCase;
|
||||
}
|
||||
|
||||
/**
|
||||
* Increment {@link #aVal} and {@link #bVal} under lock. The method is supposed to
|
||||
* be inlined into the test method and locking is supposed to be eliminated. After
|
||||
* this object escaped to the JVMTI agent, the code with eliminated locking must
|
||||
* not be used anymore.
|
||||
*/
|
||||
public synchronized void synchronizedSlowInc() {
|
||||
aVal++;
|
||||
testCase.waitingForCheck = true;
|
||||
dontinline_waitForCheck(testCase);
|
||||
testCase.waitingForCheck = false;
|
||||
bVal++;
|
||||
}
|
||||
|
||||
public static void dontinline_waitForCheck(TestCaseBase testCase) {
|
||||
if (testCase.warmUpDone) {
|
||||
while(!testCase.checkingNow) {
|
||||
try {
|
||||
Thread.sleep(50);
|
||||
} catch (InterruptedException e) { /*ign*/ }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method and incrementing {@link #aVal} and {@link #bVal} are synchronized.
|
||||
* So {@link #aVal} and {@link #bVal} should always be equal. Unless the optimized version
|
||||
* of {@link #synchronizedSlowInc()} without locking is still used after this object
|
||||
* escaped to the JVMTI agent.
|
||||
* @return
|
||||
*/
|
||||
public synchronized boolean check() {
|
||||
return aVal == bVal;
|
||||
}
|
||||
}
|
||||
|
||||
public static abstract class TestCaseBase implements Runnable {
|
||||
public final long classTag;
|
||||
public long instanceTag;
|
||||
|
||||
public final Class<?> taggedClass;
|
||||
|
||||
public long checkSum;
|
||||
public long loopCount;
|
||||
public volatile boolean doLoop;
|
||||
public volatile boolean targetIsInLoop;
|
||||
|
||||
public volatile boolean waitingForCheck;
|
||||
public volatile boolean checkingNow;
|
||||
|
||||
public boolean warmUpDone;
|
||||
|
||||
public TestCaseBase(long classTag, Class<?> taggedClass) {
|
||||
this.classTag = classTag;
|
||||
this.taggedClass = taggedClass;
|
||||
}
|
||||
|
||||
public void setUp() {
|
||||
// Tag the class of instances to be scalar replaced
|
||||
msg("tagging " + taggedClass.getName() + " with tag " + classTag);
|
||||
int err = jvmtiTagClass(taggedClass, classTag);
|
||||
Asserts.assertEQ(0, err, "jvmtiTagClass FAILED");
|
||||
}
|
||||
|
||||
// to be overridden by test cases
|
||||
abstract public void dontinline_testMethod();
|
||||
|
||||
public void warmUp() {
|
||||
msg("WarmUp: START");
|
||||
int callCount = COMPILE_THRESHOLD + 1000;
|
||||
doLoop = true;
|
||||
while (callCount-- > 0) {
|
||||
dontinline_testMethod();
|
||||
}
|
||||
warmUpDone = true;
|
||||
msg("WarmUp: DONE");
|
||||
}
|
||||
|
||||
public Object dontinline_endlessLoop(Object argEscape) {
|
||||
long cs = checkSum;
|
||||
while (loopCount-- > 0 && doLoop) {
|
||||
targetIsInLoop = true;
|
||||
checkSum += checkSum % ++cs;
|
||||
}
|
||||
loopCount = 3;
|
||||
targetIsInLoop = false;
|
||||
return argEscape;
|
||||
}
|
||||
|
||||
public void waitUntilTargetThreadHasEnteredEndlessLoop() {
|
||||
while(!targetIsInLoop) {
|
||||
msg("Target has not yet entered the loop. Sleep 100ms.");
|
||||
try { Thread.sleep(100); } catch (InterruptedException e) { /*ignore */ }
|
||||
}
|
||||
msg("Target has entered the loop.");
|
||||
}
|
||||
|
||||
public void terminateEndlessLoop() throws Exception {
|
||||
msg("Terminate endless loop");
|
||||
doLoop = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Use JVMTI heap functions associated with the elements of {@link TaggingAndCountingMethods} to
|
||||
* get a reference to an object allocated in {@link TestCase01#dontinline_testMethod()}. The
|
||||
* allocation can be eliminated / scalar replaced. The test case can be run in two modes: (1)
|
||||
* the capability can_tag_objects which is required to use the JVMTI heap functions is taken
|
||||
* before the test case (2) the capability is taken after {@link TestCase01#dontinline_testMethod()}
|
||||
* is compiled and the target thread has an activation of it on stack.
|
||||
*/
|
||||
public static class TestCase01 extends TestCaseBase {
|
||||
|
||||
public volatile int testMethod_result;
|
||||
public boolean acquireCanTagObjectsCapabilityAfterWarmup;
|
||||
public TaggingAndCountingMethods taggingMethod;
|
||||
|
||||
public TestCase01(boolean acquireCanTagObjectsCapabilityAfterWarmup, long classTag, TaggingAndCountingMethods taggingMethod) {
|
||||
super(classTag, ABBox.class);
|
||||
instanceTag = classTag + 1;
|
||||
this.acquireCanTagObjectsCapabilityAfterWarmup = acquireCanTagObjectsCapabilityAfterWarmup;
|
||||
this.taggingMethod = taggingMethod;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUp() {
|
||||
if (!acquireCanTagObjectsCapabilityAfterWarmup) {
|
||||
super.setUp();
|
||||
}
|
||||
}
|
||||
|
||||
public void setUpAfterWarmUp() {
|
||||
if (acquireCanTagObjectsCapabilityAfterWarmup) {
|
||||
msg("Acquire capability can_tag_objects " + (warmUpDone ? "after" : "before") + " warmup.");
|
||||
int err = acquireCanTagObjectsCapability();
|
||||
Asserts.assertEQ(0, err, "acquireCanTagObjectsCapability FAILED");
|
||||
super.setUp();
|
||||
}
|
||||
}
|
||||
|
||||
public void run() {
|
||||
try {
|
||||
msgHL(getClass().getName() + ": test if object that may be scalar replaced is found using " + taggingMethod);
|
||||
msg("The capability can_tag_object is acquired " + (acquireCanTagObjectsCapabilityAfterWarmup ? "AFTER" : "BEFORE")
|
||||
+ " warmup.");
|
||||
setUp();
|
||||
warmUp();
|
||||
WB.deflateIdleMonitors();
|
||||
WB.fullGC(); // get rid of dead instances from previous test cases
|
||||
runTest(taggingMethod);
|
||||
} catch (Exception e) {
|
||||
Asserts.fail("Unexpected Exception", e);
|
||||
}
|
||||
}
|
||||
|
||||
public void runTest(TaggingAndCountingMethods m) throws Exception {
|
||||
loopCount = 1L << 62; // endless loop
|
||||
doLoop = true;
|
||||
testMethod_result = 0;
|
||||
Thread t1 = new Thread(() -> dontinline_testMethod(), "Target Thread (" + getClass().getName() + ")");
|
||||
try {
|
||||
t1.start();
|
||||
try {
|
||||
waitUntilTargetThreadHasEnteredEndlessLoop();
|
||||
setUpAfterWarmUp();
|
||||
msg("count and tag instances of " + taggedClass.getName() + " with tag " + instanceTag + " using JVMTI " + m.name());
|
||||
int count = countAndTagInstancesOfClass(taggedClass, classTag, instanceTag, m);
|
||||
msg("Done. Count is " + count);
|
||||
Asserts.assertGreaterThanOrEqual(count, 0, "countAndTagInstancesOfClass FAILED");
|
||||
Asserts.assertEQ(count, 1, "unexpected number of instances");
|
||||
|
||||
ABBox[] result = new ABBox[1];
|
||||
msg("get instances tagged with " + instanceTag + ". The instances escape thereby.");
|
||||
int err = getObjectsWithTag(instanceTag, result);
|
||||
msg("Done.");
|
||||
Asserts.assertEQ(0, err, "getObjectsWithTag FAILED");
|
||||
|
||||
msg("change the now escaped instance' bVal");
|
||||
ABBox abBox = result[0];
|
||||
abBox.bVal = 3;
|
||||
terminateEndlessLoop();
|
||||
|
||||
msg("wait until target thread has set testMethod_result");
|
||||
while (testMethod_result == 0) {
|
||||
Thread.sleep(50);
|
||||
}
|
||||
msg("check if the modification of bVal is reflected in testMethod_result.");
|
||||
Asserts.assertEQ(7, testMethod_result, " testMethod_result has wrong value");
|
||||
msg("ok.");
|
||||
} finally {
|
||||
terminateEndlessLoop();
|
||||
}
|
||||
} finally {
|
||||
t1.join();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dontinline_testMethod() {
|
||||
ABBox ab = new ABBox(); // can be scalar replaced
|
||||
ab.aVal = 4;
|
||||
ab.bVal = 2;
|
||||
dontinline_endlessLoop(null); // JVMTI agent acquires reference to ab and changes bVal
|
||||
testMethod_result = ab.aVal + ab.bVal;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link #dontinline_testMethod()} creates an ArgEscape instance of {@link TestCaseBase#taggedClass} on stack.
|
||||
* The jvmti agent tags all instances of this class using one of the {@link TaggingAndCountingMethods}. Then it gets the tagged
|
||||
* instances using <code>GetObjectsWithTags()</code>. This is where the ArgEscape globally escapes.
|
||||
* It happens at a location without eliminated locking but there is
|
||||
* eliminated locking following, so the compiled frame must be deoptimized. This is checked by letting the agent call the
|
||||
* synchronized method {@link ABBox#check()} on the escaped instance.
|
||||
*/
|
||||
public static class TestCase02a extends TestCaseBase {
|
||||
|
||||
public long instanceTag;
|
||||
|
||||
public TestCase02a(long classTag) {
|
||||
super(classTag, ABBox.class);
|
||||
instanceTag = classTag + 1;
|
||||
}
|
||||
|
||||
public void run() {
|
||||
try {
|
||||
msgHL(getClass().getName() + ": test if owning frame is deoptimized if ArgEscape escapes globally");
|
||||
setUp();
|
||||
warmUp();
|
||||
for (TaggingAndCountingMethods m : TaggingAndCountingMethods.values()) {
|
||||
msgHL(getClass().getName() + ": Tag and Get of ArgEscapes using " + m.name());
|
||||
waitingForCheck = false;
|
||||
checkingNow = false;
|
||||
WB.deflateIdleMonitors();
|
||||
WB.fullGC(); // get rid of dead instances from previous test cases
|
||||
runTest(m);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Asserts.fail("Unexpected Exception", e);
|
||||
}
|
||||
}
|
||||
|
||||
public void runTest(TaggingAndCountingMethods m) throws Exception {
|
||||
loopCount = 1L << 62; // endless loop
|
||||
doLoop = true;
|
||||
Thread t1 = new Thread(() -> dontinline_testMethod(), "Target Thread (" + getClass().getName() + ")");
|
||||
try {
|
||||
t1.start();
|
||||
try {
|
||||
waitUntilTargetThreadHasEnteredEndlessLoop();
|
||||
msg("count and tag instances of " + taggedClass.getName() + " with tag " + instanceTag + " using JVMTI " + m.name());
|
||||
int count = countAndTagInstancesOfClass(taggedClass, classTag, instanceTag, m);
|
||||
msg("Done. Count is " + count);
|
||||
Asserts.assertGreaterThanOrEqual(count, 0, "countAndTagInstancesOfClass FAILED");
|
||||
Asserts.assertEQ(count, 1, "unexpected number of instances");
|
||||
} finally {
|
||||
terminateEndlessLoop();
|
||||
}
|
||||
|
||||
ABBox[] result = new ABBox[1];
|
||||
msg("get instances tagged with " + instanceTag);
|
||||
int err = getObjectsWithTag(instanceTag, result);
|
||||
msg("Done.");
|
||||
Asserts.assertEQ(0, err, "getObjectsWithTag FAILED");
|
||||
|
||||
ABBox abBoxArgEscape = result[0];
|
||||
while (!waitingForCheck) {
|
||||
Thread.yield();
|
||||
}
|
||||
msg("Check abBoxArgEscape's state is consistent");
|
||||
checkingNow = true;
|
||||
Asserts.assertTrue(abBoxArgEscape.check(), "Detected inconsistent state. abBoxArgEscape.aVal != abBoxArgEscape.bVal");
|
||||
msg("Ok.");
|
||||
} finally {
|
||||
checkingNow = true;
|
||||
t1.join();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dontinline_testMethod() {
|
||||
ABBox ab = new ABBox(this);
|
||||
dontinline_endlessLoop(ab);
|
||||
ab.synchronizedSlowInc();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Like {@link TestCase02a}, with the exception that at the location in {@link #dontinline_testMethod()} where the
|
||||
* ArgEscape escapes it is not referenced by a local variable.
|
||||
*/
|
||||
public static class TestCase02b extends TestCaseBase {
|
||||
|
||||
public long instanceTag;
|
||||
|
||||
public TestCase02b(long classTag) {
|
||||
super(classTag, ABBox.class);
|
||||
instanceTag = classTag + 1;
|
||||
}
|
||||
|
||||
public void run() {
|
||||
try {
|
||||
msgHL(getClass().getName() + ": test if owning frame is deoptimized if ArgEscape escapes globally");
|
||||
setUp();
|
||||
warmUp();
|
||||
for (TaggingAndCountingMethods m : TaggingAndCountingMethods.values()) {
|
||||
msgHL(getClass().getName() + ": Tag and Get of ArgEscapes using " + m.name());
|
||||
waitingForCheck = false;
|
||||
checkingNow = false;
|
||||
WB.deflateIdleMonitors();
|
||||
WB.fullGC(); // get rid of dead instances from previous test cases
|
||||
runTest(m);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Asserts.fail("Unexpected Exception", e);
|
||||
}
|
||||
}
|
||||
|
||||
public void runTest(TaggingAndCountingMethods m) throws Exception {
|
||||
loopCount = 1L << 62; // endless loop
|
||||
doLoop = true;
|
||||
Thread t1 = new Thread(() -> dontinline_testMethod(), "Target Thread (" + getClass().getName() + ")");
|
||||
try {
|
||||
t1.start();
|
||||
try {
|
||||
waitUntilTargetThreadHasEnteredEndlessLoop();
|
||||
msg("count and tag instances of " + taggedClass.getName() + " with tag " + instanceTag + " using JVMTI " + m.name());
|
||||
int count = countAndTagInstancesOfClass(taggedClass, classTag, instanceTag, m);
|
||||
msg("Done. Count is " + count);
|
||||
Asserts.assertGreaterThanOrEqual(count, 0, "countAndTagInstancesOfClass FAILED");
|
||||
Asserts.assertEQ(count, 1, "unexpected number of instances");
|
||||
} finally {
|
||||
terminateEndlessLoop();
|
||||
}
|
||||
|
||||
ABBox[] result = new ABBox[1];
|
||||
msg("get instances tagged with " + instanceTag);
|
||||
int err = getObjectsWithTag(instanceTag, result);
|
||||
msg("Done.");
|
||||
Asserts.assertEQ(0, err, "getObjectsWithTag FAILED");
|
||||
|
||||
ABBox abBoxArgEscape = result[0];
|
||||
while (!waitingForCheck) {
|
||||
Thread.yield();
|
||||
}
|
||||
msg("Check abBoxArgEscape's state is consistent");
|
||||
checkingNow = true;
|
||||
Asserts.assertTrue(abBoxArgEscape.check(), "Detected inconsistent state. abBoxArgEscape.aVal != abBoxArgEscape.bVal");
|
||||
msg("Ok.");
|
||||
} finally {
|
||||
checkingNow = true;
|
||||
t1.join();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dontinline_testMethod() {
|
||||
// The new instance is an ArgEscape instance and escapes to the JVMTI agent
|
||||
// while the target thread is in the call to dontinline_endlessLoop(). At this
|
||||
// location there is no local variable that references the ArgEscape.
|
||||
((ABBox) dontinline_endlessLoop(new ABBox(this))).synchronizedSlowInc();;
|
||||
}
|
||||
}
|
||||
|
||||
public static void msg(String m) {
|
||||
System.out.println();
|
||||
System.out.println("### " + m);
|
||||
System.out.println();
|
||||
}
|
||||
|
||||
public static void msgHL(String m) {
|
||||
System.out.println(); System.out.println(); System.out.println();
|
||||
System.out.println("#####################################################");
|
||||
System.out.println("### " + m);
|
||||
System.out.println("###");
|
||||
System.out.println();
|
||||
}
|
||||
}
|
@ -0,0 +1,370 @@
|
||||
/*
|
||||
* Copyright (c) 2020 SAP SE. 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 <stdio.h>
|
||||
#include <string.h>
|
||||
#include "jvmti.h"
|
||||
#include "jni.h"
|
||||
|
||||
extern "C" {
|
||||
|
||||
#define FAILED -1
|
||||
#define OK 0
|
||||
|
||||
static jvmtiEnv *jvmti;
|
||||
|
||||
static jint Agent_Initialize(JavaVM *jvm, char *options, void *reserved);
|
||||
|
||||
static void ShowErrorMessage(jvmtiEnv *jvmti, jvmtiError errCode, const char *message) {
|
||||
char *errMsg;
|
||||
jvmtiError result;
|
||||
|
||||
result = jvmti->GetErrorName(errCode, &errMsg);
|
||||
if (result == JVMTI_ERROR_NONE) {
|
||||
fprintf(stderr, "%s: %s (%d)\n", message, errMsg, errCode);
|
||||
jvmti->Deallocate((unsigned char *)errMsg);
|
||||
} else {
|
||||
fprintf(stderr, "%s (%d)\n", message, errCode);
|
||||
}
|
||||
}
|
||||
|
||||
JNIEXPORT jint JNICALL
|
||||
Agent_OnLoad(JavaVM *jvm, char *options, void *reserved) {
|
||||
return Agent_Initialize(jvm, options, reserved);
|
||||
}
|
||||
|
||||
JNIEXPORT jint JNICALL
|
||||
Agent_OnAttach(JavaVM *jvm, char *options, void *reserved) {
|
||||
return Agent_Initialize(jvm, options, reserved);
|
||||
}
|
||||
|
||||
JNIEXPORT jint JNICALL
|
||||
JNI_OnLoad(JavaVM *jvm, void *reserved) {
|
||||
jint res;
|
||||
JNIEnv *env;
|
||||
|
||||
res = jvm->GetEnv((void **) &env, JNI_VERSION_9);
|
||||
if (res != JNI_OK || env == NULL) {
|
||||
fprintf(stderr, "Error: GetEnv call failed(%d)!\n", res);
|
||||
return JNI_ERR;
|
||||
}
|
||||
|
||||
return JNI_VERSION_9;
|
||||
}
|
||||
|
||||
static jint
|
||||
Agent_Initialize(JavaVM *jvm, char *options, void *reserved) {
|
||||
jint res;
|
||||
|
||||
printf("Agent_OnLoad started\n");
|
||||
|
||||
res = jvm->GetEnv((void **) &jvmti, JVMTI_VERSION_9);
|
||||
if (res != JNI_OK || jvmti == NULL) {
|
||||
fprintf(stderr, "Error: wrong result of a valid call to GetEnv!\n");
|
||||
return JNI_ERR;
|
||||
}
|
||||
|
||||
printf("Agent_OnLoad finished\n");
|
||||
return JNI_OK;
|
||||
}
|
||||
|
||||
JNIEXPORT jint JNICALL
|
||||
Java_IterateHeapWithEscapeAnalysisEnabled_acquireCanTagObjectsCapability(JNIEnv *env, jclass cls) {
|
||||
jvmtiError err;
|
||||
jvmtiCapabilities caps;
|
||||
|
||||
memset(&caps, 0, sizeof(caps));
|
||||
|
||||
caps.can_tag_objects = 1;
|
||||
|
||||
err = jvmti->AddCapabilities(&caps);
|
||||
if (err != JVMTI_ERROR_NONE) {
|
||||
ShowErrorMessage(jvmti, err,
|
||||
"acquireCanTagObjectsCapability: error in JVMTI AddCapabilities");
|
||||
return JNI_ERR;
|
||||
}
|
||||
|
||||
err = jvmti->GetCapabilities(&caps);
|
||||
if (err != JVMTI_ERROR_NONE) {
|
||||
ShowErrorMessage(jvmti, err,
|
||||
"acquireCanTagObjectsCapability: error in JVMTI GetCapabilities");
|
||||
return JNI_ERR;
|
||||
}
|
||||
|
||||
if (!caps.can_tag_objects) {
|
||||
fprintf(stderr, "Warning: didn't get the capability can_tag_objects\n");
|
||||
return JNI_ERR;
|
||||
}
|
||||
|
||||
return JNI_OK;
|
||||
}
|
||||
|
||||
static jobject method_IterateOverReachableObjects;
|
||||
static jobject method_IterateOverHeap;
|
||||
static jobject method_IterateOverInstancesOfClass;
|
||||
static jobject method_FollowReferences;
|
||||
static jobject method_IterateThroughHeap;
|
||||
|
||||
JNIEXPORT jint JNICALL
|
||||
Java_IterateHeapWithEscapeAnalysisEnabled_registerMethod(JNIEnv *env, jclass cls, jobject method, jstring name) {
|
||||
const char *name_chars = env->GetStringUTFChars(name, 0);
|
||||
int rc = FAILED;
|
||||
if (rc != OK && strcmp(name_chars, "IterateOverReachableObjects") == 0) {
|
||||
method_IterateOverReachableObjects = env->NewGlobalRef(method);
|
||||
rc = OK;
|
||||
}
|
||||
if (rc != OK && strcmp(name_chars, "IterateOverHeap") == 0) {
|
||||
method_IterateOverHeap = env->NewGlobalRef(method);
|
||||
rc = OK;
|
||||
}
|
||||
if (rc != OK && strcmp(name_chars, "IterateOverInstancesOfClass") == 0) {
|
||||
method_IterateOverInstancesOfClass = env->NewGlobalRef(method);
|
||||
rc = OK;
|
||||
}
|
||||
if (rc != OK && strcmp(name_chars, "IterateThroughHeap") == 0) {
|
||||
method_IterateThroughHeap = env->NewGlobalRef(method);
|
||||
rc = OK;
|
||||
}
|
||||
if (rc != OK && strcmp(name_chars, "FollowReferences") == 0) {
|
||||
method_FollowReferences = env->NewGlobalRef(method);
|
||||
rc = OK;
|
||||
}
|
||||
env->ReleaseStringUTFChars(name, name_chars);
|
||||
return rc;
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_IterateHeapWithEscapeAnalysisEnabled_agentTearDown(JNIEnv *env, jclass cls) {
|
||||
env->DeleteGlobalRef(method_IterateOverReachableObjects);
|
||||
env->DeleteGlobalRef(method_IterateOverHeap);
|
||||
env->DeleteGlobalRef(method_IterateOverInstancesOfClass);
|
||||
env->DeleteGlobalRef(method_FollowReferences);
|
||||
env->DeleteGlobalRef(method_IterateThroughHeap);
|
||||
}
|
||||
|
||||
JNIEXPORT jint JNICALL
|
||||
Java_IterateHeapWithEscapeAnalysisEnabled_jvmtiTagClass(JNIEnv *env, jclass cls, jclass clsToTag, jlong tag) {
|
||||
jvmtiError err;
|
||||
err = jvmti->SetTag(clsToTag, tag);
|
||||
if (err != JVMTI_ERROR_NONE) {
|
||||
ShowErrorMessage(jvmti, err,
|
||||
"jvmtiTagClass: error in JVMTI SetTag");
|
||||
return FAILED;
|
||||
}
|
||||
return OK;
|
||||
}
|
||||
|
||||
typedef struct Tag_And_Counter {
|
||||
jlong instance_counter;
|
||||
jlong class_tag;
|
||||
jlong instance_tag;
|
||||
} Tag_And_Counter;
|
||||
|
||||
static jvmtiIterationControl JNICALL
|
||||
__stackReferenceCallback(jvmtiHeapRootKind root_kind,
|
||||
jlong class_tag,
|
||||
jlong size,
|
||||
jlong* tag_ptr,
|
||||
jlong thread_tag,
|
||||
jint depth,
|
||||
jmethodID method,
|
||||
jint slot,
|
||||
void* d) {
|
||||
Tag_And_Counter* data = (Tag_And_Counter*) d;
|
||||
if (class_tag == data->class_tag && *tag_ptr == 0) {
|
||||
data->instance_counter++;
|
||||
*tag_ptr = data->instance_tag;
|
||||
}
|
||||
return JVMTI_ITERATION_CONTINUE;
|
||||
}
|
||||
|
||||
static jvmtiIterationControl JNICALL
|
||||
__jvmtiHeapObjectCallback(jlong class_tag, jlong size, jlong* tag_ptr, void* d) {
|
||||
Tag_And_Counter* data = (Tag_And_Counter*) d;
|
||||
if (class_tag == data->class_tag && *tag_ptr == 0) {
|
||||
data->instance_counter++;
|
||||
*tag_ptr = data->instance_tag;
|
||||
}
|
||||
return JVMTI_ITERATION_CONTINUE;
|
||||
}
|
||||
|
||||
static jint JNICALL
|
||||
__jvmtiHeapReferenceCallback(jvmtiHeapReferenceKind reference_kind,
|
||||
const jvmtiHeapReferenceInfo* reference_info,
|
||||
jlong class_tag,
|
||||
jlong referrer_class_tag,
|
||||
jlong size,
|
||||
jlong* tag_ptr,
|
||||
jlong* referrer_tag_ptr,
|
||||
jint length,
|
||||
void* d) {
|
||||
Tag_And_Counter* data = (Tag_And_Counter*) d;
|
||||
if (class_tag == data->class_tag && *tag_ptr == 0) {
|
||||
data->instance_counter++;
|
||||
*tag_ptr = data->instance_tag;
|
||||
}
|
||||
return JVMTI_VISIT_OBJECTS;
|
||||
}
|
||||
|
||||
static jint JNICALL
|
||||
__jvmtiHeapIterationCallback(jlong class_tag,
|
||||
jlong size,
|
||||
jlong* tag_ptr,
|
||||
jint length,
|
||||
void* d) {
|
||||
Tag_And_Counter* data = (Tag_And_Counter*) d;
|
||||
if (class_tag == data->class_tag && *tag_ptr == 0) {
|
||||
data->instance_counter++;
|
||||
*tag_ptr = data->instance_tag;
|
||||
}
|
||||
return JVMTI_VISIT_OBJECTS;
|
||||
}
|
||||
|
||||
|
||||
JNIEXPORT jlong JNICALL
|
||||
Java_IterateHeapWithEscapeAnalysisEnabled_countAndTagInstancesOfClass(JNIEnv *env,
|
||||
jclass cls,
|
||||
jclass tagged_class,
|
||||
jlong cls_tag,
|
||||
jlong instance_tag,
|
||||
jobject method) {
|
||||
jvmtiError err;
|
||||
jvmtiHeapCallbacks callbacks = {};
|
||||
Tag_And_Counter data = {0, cls_tag, instance_tag};
|
||||
jboolean method_found = JNI_FALSE;
|
||||
|
||||
jint idx = 0;
|
||||
|
||||
if (env->IsSameObject(method, method_IterateOverReachableObjects)) {
|
||||
method_found = JNI_TRUE;
|
||||
err = jvmti->IterateOverReachableObjects(NULL /*jvmtiHeapRootCallback*/,
|
||||
__stackReferenceCallback,
|
||||
NULL /* jvmtiObjectReferenceCallback */,
|
||||
&data);
|
||||
if (err != JVMTI_ERROR_NONE) {
|
||||
ShowErrorMessage(jvmti, err,
|
||||
"countAndTagInstancesOfClass: error in JVMTI IterateOverReachableObjects");
|
||||
return FAILED;
|
||||
}
|
||||
}
|
||||
if (env->IsSameObject(method, method_IterateOverHeap)) {
|
||||
method_found = JNI_TRUE;
|
||||
err = jvmti->IterateOverHeap(JVMTI_HEAP_OBJECT_EITHER,
|
||||
__jvmtiHeapObjectCallback,
|
||||
&data);
|
||||
if (err != JVMTI_ERROR_NONE) {
|
||||
ShowErrorMessage(jvmti, err,
|
||||
"countAndTagInstancesOfClass: error in JVMTI IterateOverHeap");
|
||||
return FAILED;
|
||||
}
|
||||
}
|
||||
if (env->IsSameObject(method, method_IterateOverInstancesOfClass)) {
|
||||
method_found = JNI_TRUE;
|
||||
err = jvmti->IterateOverInstancesOfClass(tagged_class,
|
||||
JVMTI_HEAP_OBJECT_EITHER,
|
||||
__jvmtiHeapObjectCallback,
|
||||
&data);
|
||||
if (err != JVMTI_ERROR_NONE) {
|
||||
ShowErrorMessage(jvmti, err,
|
||||
"countAndTagInstancesOfClass: error in JVMTI IterateOverHeap");
|
||||
return FAILED;
|
||||
}
|
||||
}
|
||||
if (env->IsSameObject(method, method_FollowReferences)) {
|
||||
method_found = JNI_TRUE;
|
||||
callbacks.heap_reference_callback = __jvmtiHeapReferenceCallback;
|
||||
err = jvmti->FollowReferences(0 /* filter nothing */,
|
||||
NULL /* no class filter */,
|
||||
NULL /* no initial object, follow roots */,
|
||||
&callbacks,
|
||||
&data);
|
||||
if (err != JVMTI_ERROR_NONE) {
|
||||
ShowErrorMessage(jvmti, err,
|
||||
"countAndTagInstancesOfClass: error in JVMTI FollowReferences");
|
||||
return FAILED;
|
||||
}
|
||||
}
|
||||
if (env->IsSameObject(method, method_IterateThroughHeap)) {
|
||||
method_found = JNI_TRUE;
|
||||
callbacks.heap_iteration_callback = __jvmtiHeapIterationCallback;
|
||||
err = jvmti->IterateThroughHeap(0 /* filter nothing */,
|
||||
NULL /* no class filter */,
|
||||
&callbacks,
|
||||
&data);
|
||||
if (err != JVMTI_ERROR_NONE) {
|
||||
ShowErrorMessage(jvmti, err,
|
||||
"countAndTagInstancesOfClass: error in JVMTI IterateThroughHeap");
|
||||
return FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
if (!method_found) {
|
||||
fprintf(stderr, "countAndTagInstancesOfClass: unknown method\n");
|
||||
return FAILED;
|
||||
}
|
||||
|
||||
return data.instance_counter;
|
||||
}
|
||||
|
||||
JNIEXPORT jlong JNICALL
|
||||
Java_IterateHeapWithEscapeAnalysisEnabled_getObjectsWithTag(JNIEnv *env,
|
||||
jclass cls,
|
||||
jlong tag,
|
||||
jobjectArray res_instances) {
|
||||
jvmtiError err;
|
||||
const jlong tags[1] = {tag};
|
||||
jint res_count = -1;
|
||||
jobject* res_instances_raw;
|
||||
jlong* res_tags;
|
||||
jint res_instances_length;
|
||||
jint idx;
|
||||
|
||||
err = jvmti->GetObjectsWithTags(1,
|
||||
tags,
|
||||
&res_count,
|
||||
&res_instances_raw,
|
||||
&res_tags);
|
||||
if (err != JVMTI_ERROR_NONE) {
|
||||
ShowErrorMessage(jvmti, err,
|
||||
"getObjectsWithTags: error in JVMTI GetObjectsWithTags");
|
||||
return FAILED;
|
||||
}
|
||||
|
||||
res_instances_length = env->GetArrayLength(res_instances);
|
||||
if (res_count != res_instances_length) {
|
||||
fprintf(stderr, "getObjectsWithTags: result array lenght (%d) does not match instance count returned by GetObjectsWithTags (%d) \n",
|
||||
res_instances_length, res_count);
|
||||
return FAILED;
|
||||
}
|
||||
|
||||
for (idx = 0; idx < res_count; idx++) {
|
||||
env->SetObjectArrayElement(res_instances, idx, res_instances_raw[idx]);
|
||||
}
|
||||
|
||||
jvmti->Deallocate((unsigned char *)res_instances_raw);
|
||||
jvmti->Deallocate((unsigned char *)res_tags);
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
}
|
@ -56,6 +56,7 @@ requires.properties= \
|
||||
vm.debug \
|
||||
vm.hasSA \
|
||||
vm.hasJFR \
|
||||
vm.jvmci \
|
||||
docker.support \
|
||||
release.implementor
|
||||
|
||||
|
3175
test/jdk/com/sun/jdi/EATests.java
Normal file
3175
test/jdk/com/sun/jdi/EATests.java
Normal file
File diff suppressed because it is too large
Load Diff
@ -240,6 +240,7 @@ public class WhiteBox {
|
||||
public native int matchesInline(Executable method, String pattern);
|
||||
public native boolean shouldPrintAssembly(Executable method, int comp_level);
|
||||
public native int deoptimizeFrames(boolean makeNotEntrant);
|
||||
public native boolean isFrameDeoptimized(int depth);
|
||||
public native void deoptimizeAll();
|
||||
|
||||
public boolean isMethodCompiled(Executable method) {
|
||||
|
Loading…
Reference in New Issue
Block a user