Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/hotspot/share/compiler/compileBroker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
#include "compiler/compilerEvent.hpp"
#include "compiler/compilerOracle.hpp"
#include "compiler/directivesParser.hpp"
#include "gc/shared/memAllocator.hpp"
#include "interpreter/linkResolver.hpp"
#include "jvm.h"
#include "jfr/jfrEvents.hpp"
Expand Down Expand Up @@ -1396,6 +1397,7 @@ nmethod* CompileBroker::compile_method(const methodHandle& method, int osr_bci,
assert(!HAS_PENDING_EXCEPTION, "No exception should be present");
// some prerequisites that are compiler specific
if (comp->is_c2() || comp->is_jvmci()) {
InternalOOMEMark iom(THREAD);
method->constants()->resolve_string_constants(CHECK_AND_CLEAR_NONASYNC_NULL);
// Resolve all classes seen in the signature of the method
// we are compiling.
Expand Down
6 changes: 3 additions & 3 deletions src/hotspot/share/gc/shared/memAllocator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -123,21 +123,21 @@ bool MemAllocator::Allocation::check_out_of_memory() {
}

const char* message = _overhead_limit_exceeded ? "GC overhead limit exceeded" : "Java heap space";
if (!_thread->in_retryable_allocation()) {
if (!_thread->is_in_internal_oome_mark()) {
// -XX:+HeapDumpOnOutOfMemoryError and -XX:OnOutOfMemoryError support
report_java_out_of_memory(message);

if (JvmtiExport::should_post_resource_exhausted()) {
JvmtiExport::post_resource_exhausted(
JVMTI_RESOURCE_EXHAUSTED_OOM_ERROR | JVMTI_RESOURCE_EXHAUSTED_JAVA_HEAP,
message);
}

oop exception = _overhead_limit_exceeded ?
Universe::out_of_memory_error_gc_overhead_limit() :
Universe::out_of_memory_error_java_heap();
THROW_OOP_(exception, true);
} else {
THROW_OOP_(Universe::out_of_memory_error_retry(), true);
THROW_OOP_(Universe::out_of_memory_error_java_heap_without_backtrace(), true);
}
}

Expand Down
33 changes: 33 additions & 0 deletions src/hotspot/share/gc/shared/memAllocator.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -118,4 +118,37 @@ class ClassAllocator: public MemAllocator {
virtual oop initialize(HeapWord* mem) const;
};

// Manages a scope where a failed heap allocation results in
// suppression of JVMTI "resource exhausted" events and
// throwing a shared, backtrace-less OOME instance.
// Used for OOMEs that will not be propagated to user code.
class InternalOOMEMark: public StackObj {
private:
bool _outer;
JavaThread* _thread;

public:
explicit InternalOOMEMark(JavaThread* thread) {
if (thread != nullptr) {
_outer = thread->is_in_internal_oome_mark();
thread->set_is_in_internal_oome_mark(true);
_thread = thread;
} else {
_outer = false;
_thread = nullptr;
}
Comment on lines +136 to +139
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It isn't obvious to me how this part is intended to be used. I see it ties back to the retryable allocation "activate" mode, but I'm unclear what that means as well.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

By "this part", do you mean the else branch? It exists for the !activate case of RetryableAllocationMark which is used when the null_on_fail parameter of JVMCIRuntime::new_instance_common is true. That is, the runtime call is from compiled code that does not want to trigger throwing of an OOME. Graal will deopt in such cases and let the interpreter throw the exception. This ensures the OOME is reported exactly once to JVMTI.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"this part" means the "else branch" which means the null receiving constructor. Yeah that whole "null_on_fail" thing had me a bit perplexed and I see there is now a JBS issue filed. to kill it off as we always want null-on-fail.

}

~InternalOOMEMark() {
if (_thread != nullptr) {
// Check that only InternalOOMEMark sets
// JavaThread::_is_in_internal_oome_mark
assert(_thread->is_in_internal_oome_mark(), "must be");
_thread->set_is_in_internal_oome_mark(_outer);
}
}

JavaThread* thread() const { return _thread; }
};

#endif // SHARE_GC_SHARED_MEMALLOCATOR_HPP
35 changes: 11 additions & 24 deletions src/hotspot/share/jvmci/jvmciRuntime.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
#include "classfile/vmClasses.hpp"
#include "compiler/compileBroker.hpp"
#include "gc/shared/collectedHeap.hpp"
#include "gc/shared/memAllocator.hpp"
#include "gc/shared/oopStorage.inline.hpp"
#include "jvmci/jniAccessMark.inline.hpp"
#include "jvmci/jvmciCompilerToVM.hpp"
Expand Down Expand Up @@ -92,39 +93,25 @@ static void deopt_caller() {
}

// Manages a scope for a JVMCI runtime call that attempts a heap allocation.
// If there is a pending nonasync exception upon closing the scope and the runtime
// If there is a pending OutOfMemoryError upon closing the scope and the runtime
// call is of the variety where allocation failure returns null without an
// exception, the following action is taken:
// 1. The pending nonasync exception is cleared
// 1. The pending OutOfMemoryError is cleared
// 2. null is written to JavaThread::_vm_result
// 3. Checks that an OutOfMemoryError is Universe::out_of_memory_error_retry().
class RetryableAllocationMark: public StackObj {
class RetryableAllocationMark {
private:
JavaThread* _thread;
InternalOOMEMark _iom;
public:
RetryableAllocationMark(JavaThread* thread, bool activate) {
if (activate) {
assert(!thread->in_retryable_allocation(), "retryable allocation scope is non-reentrant");
_thread = thread;
_thread->set_in_retryable_allocation(true);
} else {
_thread = nullptr;
}
}
RetryableAllocationMark(JavaThread* thread, bool activate) : _iom(activate ? thread : nullptr) {}
~RetryableAllocationMark() {
if (_thread != nullptr) {
_thread->set_in_retryable_allocation(false);
JavaThread* THREAD = _thread; // For exception macros.
JavaThread* THREAD = _iom.thread(); // For exception macros.
if (THREAD != nullptr) {
if (HAS_PENDING_EXCEPTION) {
oop ex = PENDING_EXCEPTION;
// Do not clear probable async exceptions.
CLEAR_PENDING_NONASYNC_EXCEPTION;
oop retry_oome = Universe::out_of_memory_error_retry();
if (ex->is_a(retry_oome->klass()) && retry_oome != ex) {
ResourceMark rm;
fatal("Unexpected exception in scope of retryable allocation: " INTPTR_FORMAT " of type %s", p2i(ex), ex->klass()->external_name());
THREAD->set_vm_result(nullptr);
if (ex->is_a(vmClasses::OutOfMemoryError_klass())) {
CLEAR_PENDING_EXCEPTION;
}
Comment on lines +112 to 114
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just an observation but the original code will clear all exceptions except for an "async" exception, which these days is only the InternalError thrown by unsafe-access-errors. But the new code will only clear OOME thus allowing the (as expected) InternalError to remain, but also any other VirtualMachineErrors that may have arisen e.g. StackOverflowError. I actually think this is more correct, but it does seem a change in behaviour that we may need to be wary of.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the context of Graal, it doesn't really make much of a difference as the Graal stub that calls this runtime routine will clear all exceptions anyway. But yes, I think limiting the clearing here to OOME is better.

_thread->set_vm_result(nullptr);
}
}
}
Expand Down
11 changes: 4 additions & 7 deletions src/hotspot/share/memory/universe.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,6 @@ enum OutOfMemoryInstance { _oom_java_heap,
_oom_array_size,
_oom_gc_overhead_limit,
_oom_realloc_objects,
_oom_retry,
_oom_count };

OopHandle Universe::_out_of_memory_errors;
Expand Down Expand Up @@ -655,6 +654,10 @@ oop Universe::out_of_memory_error_java_heap() {
return gen_out_of_memory_error(out_of_memory_errors()->obj_at(_oom_java_heap));
}

oop Universe::out_of_memory_error_java_heap_without_backtrace() {
return out_of_memory_errors()->obj_at(_oom_java_heap);
}

oop Universe::out_of_memory_error_c_heap() {
return gen_out_of_memory_error(out_of_memory_errors()->obj_at(_oom_c_heap));
}
Expand All @@ -679,9 +682,6 @@ oop Universe::out_of_memory_error_realloc_objects() {
return gen_out_of_memory_error(out_of_memory_errors()->obj_at(_oom_realloc_objects));
}

// Throw default _out_of_memory_error_retry object as it will never propagate out of the VM
oop Universe::out_of_memory_error_retry() { return out_of_memory_errors()->obj_at(_oom_retry); }

oop Universe::class_init_out_of_memory_error() { return out_of_memory_errors()->obj_at(_oom_java_heap); }
oop Universe::class_init_stack_overflow_error() { return _class_init_stack_overflow_error.resolve(); }
oop Universe::delayed_stack_overflow_error_message() { return _delayed_stack_overflow_error_message.resolve(); }
Expand Down Expand Up @@ -785,9 +785,6 @@ void Universe::create_preallocated_out_of_memory_errors(TRAPS) {
msg = java_lang_String::create_from_str("Java heap space: failed reallocation of scalar replaced objects", CHECK);
java_lang_Throwable::set_message(oom_array->obj_at(_oom_realloc_objects), msg());

msg = java_lang_String::create_from_str("Java heap space: failed retryable allocation", CHECK);
java_lang_Throwable::set_message(oom_array->obj_at(_oom_retry), msg());

// Setup the array of errors that have preallocated backtrace
int len = (StackTraceInThrowable) ? (int)PreallocatedOutOfMemoryErrorCount : 0;
objArrayOop instance = oopFactory::new_objArray(ik, len, CHECK);
Expand Down
3 changes: 1 addition & 2 deletions src/hotspot/share/memory/universe.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -272,15 +272,14 @@ class Universe: AllStatic {
// may or may not have a backtrace. If error has a backtrace then the stack trace is already
// filled in.
static oop out_of_memory_error_java_heap();
static oop out_of_memory_error_java_heap_without_backtrace();
static oop out_of_memory_error_c_heap();
static oop out_of_memory_error_metaspace();
static oop out_of_memory_error_class_metaspace();
static oop out_of_memory_error_array_size();
static oop out_of_memory_error_gc_overhead_limit();
static oop out_of_memory_error_realloc_objects();

// Throw default _out_of_memory_error_retry object as it will never propagate out of the VM
static oop out_of_memory_error_retry();
static oop delayed_stack_overflow_error_message();

// Saved StackOverflowError and OutOfMemoryError for use when
Expand Down
4 changes: 2 additions & 2 deletions src/hotspot/share/oops/klass.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -873,12 +873,12 @@ void Klass::set_archived_java_mirror(int mirror_index) {

void Klass::check_array_allocation_length(int length, int max_length, TRAPS) {
if (length > max_length) {
if (!THREAD->in_retryable_allocation()) {
if (!THREAD->is_in_internal_oome_mark()) {
report_java_out_of_memory("Requested array size exceeds VM limit");
JvmtiExport::post_array_size_exhausted();
THROW_OOP(Universe::out_of_memory_error_array_size());
} else {
THROW_OOP(Universe::out_of_memory_error_retry());
THROW_OOP(Universe::out_of_memory_error_java_heap_without_backtrace());
}
} else if (length < 0) {
THROW_MSG(vmSymbols::java_lang_NegativeArraySizeException(), err_msg("%d", length));
Expand Down
4 changes: 4 additions & 0 deletions src/hotspot/share/runtime/deoptimization.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
#include "compiler/compilationPolicy.hpp"
#include "compiler/compilerDefinitions.inline.hpp"
#include "gc/shared/collectedHeap.hpp"
#include "gc/shared/memAllocator.hpp"
#include "interpreter/bytecode.hpp"
#include "interpreter/bytecodeStream.hpp"
#include "interpreter/interpreter.hpp"
Expand Down Expand Up @@ -1250,6 +1251,7 @@ bool Deoptimization::realloc_objects(JavaThread* thread, frame* fr, RegisterMap*

InstanceKlass* ik = InstanceKlass::cast(k);
if (obj == nullptr && !cache_init_error) {
InternalOOMEMark iom(THREAD);
#if COMPILER2_OR_JVMCI
if (EnableVectorSupport && VectorSupport::is_vector(ik)) {
obj = VectorSupport::allocate_vector(ik, fr, reg_map, sv, THREAD);
Expand All @@ -1264,9 +1266,11 @@ bool Deoptimization::realloc_objects(JavaThread* thread, frame* fr, RegisterMap*
TypeArrayKlass* ak = TypeArrayKlass::cast(k);
assert(sv->field_size() % type2size[ak->element_type()] == 0, "non-integral array length");
int len = sv->field_size() / type2size[ak->element_type()];
InternalOOMEMark iom(THREAD);
obj = ak->allocate(len, THREAD);
} else if (k->is_objArray_klass()) {
ObjArrayKlass* ak = ObjArrayKlass::cast(k);
InternalOOMEMark iom(THREAD);
obj = ak->allocate(sv->field_size(), THREAD);
}

Expand Down
2 changes: 1 addition & 1 deletion src/hotspot/share/runtime/javaThread.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -455,11 +455,11 @@ JavaThread::JavaThread() :
#endif
#endif
_jni_attach_state(_not_attaching_via_jni),
_is_in_internal_oome_mark(false),
#if INCLUDE_JVMCI
_pending_deoptimization(-1),
_pending_monitorenter(false),
_pending_transfer_to_interpreter(false),
_in_retryable_allocation(false),
_pending_failed_speculation(0),
_jvmci{nullptr},
_libjvmci_runtime(nullptr),
Expand Down
14 changes: 7 additions & 7 deletions src/hotspot/share/runtime/javaThread.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
class AsyncExceptionHandshake;
class ContinuationEntry;
class DeoptResourceMark;
class InternalOOMEMark;
class JNIHandleBlock;
class JVMCIRuntime;

Expand Down Expand Up @@ -334,6 +335,8 @@ class JavaThread: public Thread {
// of _attaching_via_jni and transitions to _attached_via_jni.
volatile JNIAttachStates _jni_attach_state;

// In scope of an InternalOOMEMark?
bool _is_in_internal_oome_mark;

#if INCLUDE_JVMCI
// The _pending_* fields below are used to communicate extra information
Expand All @@ -349,10 +352,6 @@ class JavaThread: public Thread {
// Specifies if the DeoptReason for the last uncommon trap was Reason_transfer_to_interpreter
bool _pending_transfer_to_interpreter;

// True if in a runtime call from compiled code that will deoptimize
// and re-execute a failed heap allocation in the interpreter.
bool _in_retryable_allocation;

// An id of a speculation that JVMCI compiled code can use to further describe and
// uniquely identify the speculative optimization guarded by an uncommon trap.
// See JVMCINMethodData::SPECULATION_LENGTH_BITS for further details.
Expand Down Expand Up @@ -714,6 +713,10 @@ class JavaThread: public Thread {
MemRegion deferred_card_mark() const { return _deferred_card_mark; }
void set_deferred_card_mark(MemRegion mr) { _deferred_card_mark = mr; }

// Is thread in scope of an InternalOOMEMark?
bool is_in_internal_oome_mark() const { return _is_in_internal_oome_mark; }
void set_is_in_internal_oome_mark(bool b) { _is_in_internal_oome_mark = b; }

#if INCLUDE_JVMCI
jlong pending_failed_speculation() const { return _pending_failed_speculation; }
void set_pending_monitorenter(bool b) { _pending_monitorenter = b; }
Expand All @@ -723,9 +726,6 @@ class JavaThread: public Thread {
void set_jvmci_alternate_call_target(address a) { assert(_jvmci._alternate_call_target == nullptr, "must be"); _jvmci._alternate_call_target = a; }
void set_jvmci_implicit_exception_pc(address a) { assert(_jvmci._implicit_exception_pc == nullptr, "must be"); _jvmci._implicit_exception_pc = a; }

virtual bool in_retryable_allocation() const { return _in_retryable_allocation; }
void set_in_retryable_allocation(bool b) { _in_retryable_allocation = b; }

JVMCIRuntime* libjvmci_runtime() const { return _libjvmci_runtime; }
void set_libjvmci_runtime(JVMCIRuntime* rt) {
assert((_libjvmci_runtime == nullptr && rt != nullptr) || (_libjvmci_runtime != nullptr && rt == nullptr), "must be");
Expand Down
8 changes: 0 additions & 8 deletions src/hotspot/share/runtime/thread.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -210,14 +210,6 @@ class Thread: public ThreadShadow {
DEBUG_ONLY(bool _indirectly_safepoint_thread;)

public:
// Determines if a heap allocation failure will be retried
// (e.g., by deoptimizing and re-executing in the interpreter).
// In this case, the failed allocation must raise
// Universe::out_of_memory_error_retry() and omit side effects
// such as JVMTI events and handling -XX:+HeapDumpOnOutOfMemoryError
// and -XX:OnOutOfMemoryError.
virtual bool in_retryable_allocation() const { return false; }

#ifdef ASSERT
void set_suspendible_thread() { _suspendible_thread = true; }
void clear_suspendible_thread() { _suspendible_thread = false; }
Expand Down