Skip to content

Commit 422128b

Browse files
author
Doug Simon
committed
8306992: [JVMCI] mitigate more against JVMCI related OOME causing VM to exit
Reviewed-by: never
1 parent fe8c689 commit 422128b

File tree

10 files changed

+260
-77
lines changed

10 files changed

+260
-77
lines changed

src/hotspot/share/jvmci/jvmci.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -233,12 +233,12 @@ void JVMCI::vtrace(int level, const char* format, va_list ap) {
233233
ResourceMark rm(thread);
234234
JavaThreadState state = JavaThread::cast(thread)->thread_state();
235235
if (state == _thread_in_vm || state == _thread_in_Java || state == _thread_new) {
236-
tty->print("JVMCITrace-%d[%s]:%*c", level, thread->name(), level, ' ');
236+
tty->print("JVMCITrace-%d[" PTR_FORMAT " \"%s\"]:%*c", level, p2i(thread), thread->name(), level, ' ');
237237
} else {
238238
// According to check_access_thread_state, it's unsafe to
239239
// resolve the j.l.Thread object unless the thread is in
240240
// one of the states above.
241-
tty->print("JVMCITrace-%d[%s@" PTR_FORMAT "]:%*c", level, thread->type_name(), p2i(thread), level, ' ');
241+
tty->print("JVMCITrace-%d[" PTR_FORMAT " <%s>]:%*c", level, p2i(thread), thread->type_name(), level, ' ');
242242
}
243243
} else {
244244
tty->print("JVMCITrace-%d[?]:%*c", level, level, ' ');

src/hotspot/share/jvmci/jvmciCompiler.cpp

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@ JVMCICompiler::JVMCICompiler() : AbstractCompiler(compiler_jvmci) {
3939
_bootstrapping = false;
4040
_bootstrap_compilation_request_handled = false;
4141
_methods_compiled = 0;
42+
_ok_upcalls = 0;
43+
_err_upcalls = 0;
44+
_disabled = false;
4245
_global_compilation_ticks = 0;
4346
assert(_instance == nullptr, "only one instance allowed");
4447
_instance = this;
@@ -118,6 +121,9 @@ void JVMCICompiler::bootstrap(TRAPS) {
118121
}
119122

120123
bool JVMCICompiler::force_comp_at_level_simple(const methodHandle& method) {
124+
if (_disabled) {
125+
return true;
126+
}
121127
if (_bootstrapping) {
122128
// When bootstrapping, the JVMCI compiler can compile its own methods.
123129
return false;
@@ -211,6 +217,39 @@ void JVMCICompiler::inc_methods_compiled() {
211217
Atomic::inc(&_global_compilation_ticks);
212218
}
213219

220+
void JVMCICompiler::on_upcall(const char* error, JVMCICompileState* compile_state) {
221+
if (error != nullptr) {
222+
223+
Atomic::inc(&_err_upcalls);
224+
int ok = _ok_upcalls;
225+
int err = _err_upcalls;
226+
// If there have been at least 10 upcalls with an error
227+
// and the number of error upcalls is 10% or more of the
228+
// number of non-error upcalls, disable JVMCI compilation.
229+
if (err > 10 && err * 10 > ok && !_disabled) {
230+
_disabled = true;
231+
int total = err + ok;
232+
const char* disable_msg = err_msg("JVMCI compiler disabled "
233+
"after %d of %d upcalls had errors (Last error: \"%s\"). "
234+
"Use -Xlog:jit+compilation for more detail.", err, total, error);
235+
log_warning(jit,compilation)("%s", disable_msg);
236+
if (compile_state != nullptr) {
237+
const char* disable_error = os::strdup(disable_msg);
238+
if (disable_error != nullptr) {
239+
compile_state->set_failure(true, disable_error, true);
240+
JVMCI_event_1("%s", disable_error);
241+
return;
242+
} else {
243+
// Leave failure reason as set by caller when strdup fails
244+
}
245+
}
246+
}
247+
JVMCI_event_1("JVMCI upcall had an error: %s", error);
248+
} else {
249+
Atomic::inc(&_ok_upcalls);
250+
}
251+
}
252+
214253
void JVMCICompiler::inc_global_compilation_ticks() {
215254
Atomic::inc(&_global_compilation_ticks);
216255
}

src/hotspot/share/jvmci/jvmciCompiler.hpp

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2011, 2020, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2011, 2023, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -28,6 +28,8 @@
2828
#include "compiler/compiler_globals.hpp"
2929
#include "runtime/atomic.hpp"
3030

31+
class JVMCICompileState;
32+
3133
class JVMCICompiler : public AbstractCompiler {
3234
public:
3335
// Code installation specific statistics.
@@ -62,10 +64,20 @@ class JVMCICompiler : public AbstractCompiler {
6264

6365
/**
6466
* Number of methods successfully compiled by a call to
65-
* JVMCICompiler::compile_method().
67+
* JVMCIRuntime::compile_method().
6668
*/
6769
volatile int _methods_compiled;
6870

71+
// Tracks upcalls that should only fail under severe conditions (e.g.
72+
// memory pressure) and disables JVMCI compilation if too many fail
73+
// with an error. A good example is an OOME thrown
74+
// when libgraal calls into the HotSpot heap to get a copy
75+
// of the system properties or to translate an exception from
76+
// the HotSpot heap to the libgraal heap.
77+
volatile int _ok_upcalls;
78+
volatile int _err_upcalls;
79+
bool _disabled;
80+
6981
// Incremented periodically by JVMCI compiler threads
7082
// to indicate JVMCI compilation activity.
7183
volatile int _global_compilation_ticks;
@@ -126,6 +138,11 @@ class JVMCICompiler : public AbstractCompiler {
126138
int methods_compiled() { return _methods_compiled; }
127139
void inc_methods_compiled();
128140

141+
// Called after a JVMCI upcall whose success is a measure of the
142+
// JVMCI compiler's health. The value of `error` describes
143+
// an error during the upcall, null if no error.
144+
void on_upcall(const char* error, JVMCICompileState* compile_state=nullptr);
145+
129146
// Gets a value indicating JVMCI compilation activity on any thread.
130147
// If successive calls to this method return a different value, then
131148
// some degree of JVMCI compilation occurred between the calls.

src/hotspot/share/jvmci/jvmciCompilerToVM.cpp

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2399,8 +2399,12 @@ C2V_VMENTRY_NULL(jlongArray, registerNativeMethods, (JNIEnv* env, jobject, jclas
23992399
JVMCIRuntime* runtime;
24002400
{
24012401
// Ensure the JVMCI shared library runtime is initialized.
2402-
JVMCIEnv __peer_jvmci_env__(thread, false, __FILE__, __LINE__);
2402+
bool jni_enomem_is_fatal = false;
2403+
JVMCIEnv __peer_jvmci_env__(thread, false, jni_enomem_is_fatal, __FILE__, __LINE__);
24032404
JVMCIEnv* peerEnv = &__peer_jvmci_env__;
2405+
if (peerEnv->has_jni_enomem()) {
2406+
JVMCI_THROW_MSG_0(OutOfMemoryError, "JNI_ENOMEM creating or attaching to libjvmci");
2407+
}
24042408
HandleMark hm(THREAD);
24052409
runtime = JVMCI::compiler_runtime(thread);
24062410
if (peerEnv->has_pending_exception()) {
@@ -2563,8 +2567,13 @@ C2V_VMENTRY_PREFIX(jboolean, attachCurrentThread, (JNIEnv* env, jobject c2vm, jb
25632567

25642568
{
25652569
// Ensure the JVMCI shared library runtime is initialized.
2566-
JVMCIEnv __peer_jvmci_env__(thread, false, __FILE__, __LINE__);
2570+
bool jni_enomem_is_fatal = false;
2571+
JVMCIEnv __peer_jvmci_env__(thread, false, jni_enomem_is_fatal, __FILE__, __LINE__);
25672572
JVMCIEnv* peerJVMCIEnv = &__peer_jvmci_env__;
2573+
if (peerJVMCIEnv->has_jni_enomem()) {
2574+
JVMCI_THROW_MSG_0(OutOfMemoryError, "JNI_ENOMEM creating or attaching to libjvmci");
2575+
}
2576+
25682577
HandleMark hm(thread);
25692578
JVMCIObject receiver = runtime->get_HotSpotJVMCIRuntime(peerJVMCIEnv);
25702579
if (peerJVMCIEnv->has_pending_exception()) {
@@ -2658,9 +2667,13 @@ C2V_VMENTRY_0(jlong, translate, (JNIEnv* env, jobject, jobject obj_handle, jbool
26582667
if (obj_handle == nullptr) {
26592668
return 0L;
26602669
}
2661-
JVMCIEnv __peer_jvmci_env__(thread, !JVMCIENV->is_hotspot(), __FILE__, __LINE__);
2670+
bool jni_enomem_is_fatal = false;
2671+
JVMCIEnv __peer_jvmci_env__(thread, !JVMCIENV->is_hotspot(), jni_enomem_is_fatal, __FILE__, __LINE__);
26622672
JVMCIEnv* peerEnv = &__peer_jvmci_env__;
26632673
JVMCIEnv* thisEnv = JVMCIENV;
2674+
if (peerEnv->has_jni_enomem()) {
2675+
JVMCI_THROW_MSG_0(OutOfMemoryError, "JNI_ENOMEM creating or attaching to libjvmci");
2676+
}
26642677

26652678
JVMCIObject obj = thisEnv->wrap(obj_handle);
26662679
JVMCIObject result;

src/hotspot/share/jvmci/jvmciEnv.cpp

Lines changed: 40 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,21 @@ JVMCICompileState::JVMCICompileState(CompileTask* task, JVMCICompiler* compiler)
6767
}
6868
}
6969

70+
void JVMCICompileState::set_failure(bool retryable, const char* reason, bool reason_on_C_heap) {
71+
if (_failure_reason != nullptr && _failure_reason_on_C_heap) {
72+
os::free((void*) _failure_reason);
73+
}
74+
_failure_reason = reason;
75+
_failure_reason_on_C_heap = reason_on_C_heap;
76+
_retryable = retryable;
77+
}
78+
79+
void JVMCICompileState::notify_libjvmci_oome() {
80+
const char* msg = "Out of memory initializing libjvmci or attaching it to the current thread";
81+
set_failure(true, msg);
82+
_compiler->on_upcall(msg);
83+
}
84+
7085
// Update global JVMCI compilation ticks after 512 thread-local JVMCI compilation ticks.
7186
// This mitigates the overhead of the atomic operation used for the global update.
7287
#define THREAD_TICKS_PER_GLOBAL_TICKS (2 << 9)
@@ -172,7 +187,7 @@ void JVMCIEnv::copy_saved_properties(jbyte* properties, int properties_len, JVMC
172187
}
173188
}
174189

175-
void JVMCIEnv::init_env_mode_runtime(JavaThread* thread, JNIEnv* parent_env, bool attach_OOME_is_fatal) {
190+
void JVMCIEnv::init_env_mode_runtime(JavaThread* thread, JNIEnv* parent_env, bool jni_enomem_is_fatal) {
176191
assert(thread != nullptr, "npe");
177192
_env = nullptr;
178193
_pop_frame_on_close = false;
@@ -204,11 +219,18 @@ void JVMCIEnv::init_env_mode_runtime(JavaThread* thread, JNIEnv* parent_env, boo
204219
_is_hotspot = false;
205220

206221
_runtime = JVMCI::compiler_runtime(thread);
207-
_env = _runtime->init_shared_library_javavm();
208-
222+
int create_JavaVM_err = JNI_OK;
223+
_env = _runtime->init_shared_library_javavm(&create_JavaVM_err);
209224
if (_env != nullptr) {
210225
// Creating the JVMCI shared library VM also attaches the current thread
211226
_detach_on_close = true;
227+
} else if (create_JavaVM_err != JNI_OK) {
228+
if (!jni_enomem_is_fatal && create_JavaVM_err == JNI_ENOMEM) {
229+
_jni_enomem = true;
230+
return;
231+
} else {
232+
fatal("JNI_CreateJavaVM failed with return value %d", create_JavaVM_err);
233+
}
212234
} else {
213235
_runtime->GetEnv(thread, (void**)&parent_env, JNI_VERSION_1_2);
214236
if (parent_env != nullptr) {
@@ -227,9 +249,9 @@ void JVMCIEnv::init_env_mode_runtime(JavaThread* thread, JNIEnv* parent_env, boo
227249
jint attach_result = _runtime->AttachCurrentThread(thread, (void**) &_env, &attach_args);
228250
if (attach_result == JNI_OK) {
229251
_detach_on_close = true;
230-
} else if (!attach_OOME_is_fatal && attach_result == JNI_ENOMEM) {
252+
} else if (!jni_enomem_is_fatal && attach_result == JNI_ENOMEM) {
231253
_env = nullptr;
232-
_attach_threw_OOME = true;
254+
_jni_enomem = true;
233255
return;
234256
} else {
235257
fatal("Error attaching current thread (%s) to JVMCI shared library JNI interface", attach_args.name);
@@ -251,40 +273,41 @@ void JVMCIEnv::init_env_mode_runtime(JavaThread* thread, JNIEnv* parent_env, boo
251273
}
252274

253275
JVMCIEnv::JVMCIEnv(JavaThread* thread, JVMCICompileState* compile_state, const char* file, int line):
254-
_throw_to_caller(false), _file(file), _line(line), _attach_threw_OOME(false), _compile_state(compile_state) {
255-
// In case of OOME, there's a good chance a subsequent attempt to attach might succeed.
256-
// Other errors most likely indicate a non-recoverable error in the JVMCI runtime.
257-
init_env_mode_runtime(thread, nullptr, false);
258-
if (_attach_threw_OOME) {
276+
_throw_to_caller(false), _file(file), _line(line), _jni_enomem(false), _compile_state(compile_state) {
277+
// In case of JNI_ENOMEM, there's a good chance a subsequent attempt to create libjvmci or attach to it
278+
// might succeed. Other errors most likely indicate a non-recoverable error in the JVMCI runtime.
279+
bool jni_enomem_is_fatal = false;
280+
init_env_mode_runtime(thread, nullptr, jni_enomem_is_fatal);
281+
if (_jni_enomem) {
259282
compile_state->set_failure(true, "Out of memory while attaching JVMCI compiler to current thread");
260283
}
261284
}
262285

263286
JVMCIEnv::JVMCIEnv(JavaThread* thread, const char* file, int line):
264-
_throw_to_caller(false), _file(file), _line(line), _attach_threw_OOME(false), _compile_state(nullptr) {
287+
_throw_to_caller(false), _file(file), _line(line), _jni_enomem(false), _compile_state(nullptr) {
265288
init_env_mode_runtime(thread, nullptr);
266289
}
267290

268291
JVMCIEnv::JVMCIEnv(JavaThread* thread, JNIEnv* parent_env, const char* file, int line):
269-
_throw_to_caller(true), _file(file), _line(line), _attach_threw_OOME(false), _compile_state(nullptr) {
292+
_throw_to_caller(true), _file(file), _line(line), _jni_enomem(false), _compile_state(nullptr) {
270293
init_env_mode_runtime(thread, parent_env);
271294
assert(_env == nullptr || parent_env == _env, "mismatched JNIEnvironment");
272295
}
273296

274-
void JVMCIEnv::init(JavaThread* thread, bool is_hotspot, const char* file, int line) {
297+
void JVMCIEnv::init(JavaThread* thread, bool is_hotspot, bool jni_enomem_is_fatal, const char* file, int line) {
275298
_compile_state = nullptr;
276299
_throw_to_caller = false;
277300
_file = file;
278301
_line = line;
279-
_attach_threw_OOME = false;
302+
_jni_enomem = false;
280303
if (is_hotspot) {
281304
_env = nullptr;
282305
_pop_frame_on_close = false;
283306
_detach_on_close = false;
284307
_is_hotspot = true;
285308
_runtime = JVMCI::java_runtime();
286309
} else {
287-
init_env_mode_runtime(thread, nullptr);
310+
init_env_mode_runtime(thread, nullptr, jni_enomem_is_fatal);
288311
}
289312
}
290313

@@ -464,7 +487,7 @@ jboolean JVMCIEnv::transfer_pending_exception(JavaThread* THREAD, JVMCIEnv* peer
464487
}
465488

466489
JVMCIEnv::~JVMCIEnv() {
467-
if (_attach_threw_OOME) {
490+
if (_jni_enomem) {
468491
return;
469492
}
470493
if (_throw_to_caller) {
@@ -775,6 +798,7 @@ DO_THROW(IllegalArgumentException)
775798
DO_THROW(InvalidInstalledCodeException)
776799
DO_THROW(UnsatisfiedLinkError)
777800
DO_THROW(UnsupportedOperationException)
801+
DO_THROW(OutOfMemoryError)
778802
DO_THROW(ClassNotFoundException)
779803

780804
#undef DO_THROW

src/hotspot/share/jvmci/jvmciEnv.hpp

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -137,11 +137,11 @@ class JVMCICompileState : public ResourceObj {
137137
bool failure_reason_on_C_heap() { return _failure_reason_on_C_heap; }
138138
bool retryable() { return _retryable; }
139139

140-
void set_failure(bool retryable, const char* reason, bool reason_on_C_heap = false) {
141-
_failure_reason = reason;
142-
_failure_reason_on_C_heap = reason_on_C_heap;
143-
_retryable = retryable;
144-
}
140+
void set_failure(bool retryable, const char* reason, bool reason_on_C_heap = false);
141+
142+
// Called when creating or attaching to a libjvmci isolate failed
143+
// due to an out of memory condition.
144+
void notify_libjvmci_oome();
145145

146146
jint compilation_ticks() const { return _compilation_ticks; }
147147
void inc_compilation_ticks();
@@ -157,9 +157,9 @@ class JVMCIEnv : public ResourceObj {
157157
friend class JNIAccessMark;
158158

159159
// Initializes the _env, _mode and _runtime fields.
160-
void init_env_mode_runtime(JavaThread* thread, JNIEnv* parent_env, bool attach_OOME_is_fatal = true);
160+
void init_env_mode_runtime(JavaThread* thread, JNIEnv* parent_env, bool jni_enomem_is_fatal = true);
161161

162-
void init(JavaThread* thread, bool is_hotspot, const char* file, int line);
162+
void init(JavaThread* thread, bool is_hotspot, bool jni_enomem_is_fatal, const char* file, int line);
163163

164164
JNIEnv* _env; // JNI env for calling into shared library
165165
bool _pop_frame_on_close; // Must pop frame on close?
@@ -169,7 +169,9 @@ class JVMCIEnv : public ResourceObj {
169169
bool _throw_to_caller; // Propagate an exception raised in this env to the caller?
170170
const char* _file; // The file and ...
171171
int _line; // ... line where this JNIEnv was created
172-
bool _attach_threw_OOME; // Failed to attach thread due to OutOfMemoryError, the JVMCIEnv is invalid
172+
bool _jni_enomem; // JNI_ENOMEM returned when creating or attaching to a libjvmci isolate.
173+
// If true, the JVMCIEnv is invalid and should not be used apart from
174+
// calling has_jni_enomem().
173175

174176
// Translates an exception on the HotSpot heap (i.e., hotspot_env) to an exception on
175177
// the shared library heap (i.e., jni_env). The translation includes the stack and cause(s) of `throwable`.
@@ -212,18 +214,25 @@ class JVMCIEnv : public ResourceObj {
212214
// on the VM thread.
213215
assert(for_object.is_hotspot() || !Thread::current()->is_VM_thread(),
214216
"cannot open JVMCIEnv scope when in the VM thread for accessing a shared library heap object");
215-
init(thread, for_object.is_hotspot(), file, line);
217+
bool jni_enomem_is_fatal = true;
218+
init(thread, for_object.is_hotspot(), jni_enomem_is_fatal, file, line);
216219
}
217220

218221
// Opens a JNIEnv scope for the HotSpot runtime if `is_hotspot` is true
219222
// otherwise for the shared library runtime. An exception occurring
220223
// within the scope must not be propagated back to the caller.
221-
JVMCIEnv(JavaThread* thread, bool is_hotspot, const char* file, int line) {
222-
init(thread, is_hotspot, file, line);
224+
JVMCIEnv(JavaThread* thread, bool is_hotspot, bool jni_enomem_is_fatal, const char* file, int line) {
225+
init(thread, is_hotspot, jni_enomem_is_fatal, file, line);
223226
}
224227

225228
~JVMCIEnv();
226229

230+
// Determines if a JNI_ENOMEM occurred while trying to create a libjvmci
231+
// isolate or attach to it within the scope of a JVMCIEnv constructor.
232+
bool has_jni_enomem() {
233+
return _jni_enomem;
234+
}
235+
227236
JVMCIRuntime* runtime() {
228237
return _runtime;
229238
}
@@ -249,7 +258,9 @@ class JVMCIEnv : public ResourceObj {
249258
// Returns true if a pending exception was transferred, false otherwise.
250259
static jboolean transfer_pending_exception_to_jni(JavaThread* THREAD, JVMCIEnv* hotspot_env, JVMCIEnv* jni_env);
251260

252-
// Prints an exception and stack trace of a pending exception.
261+
// Prints the toString() and stack trace of a pending exception.
262+
// If there is no pending exception, this is a nop.
263+
// If `clear` is false, the pending exception will remain pending upon return.
253264
void describe_pending_exception(bool clear);
254265

255266
int get_length(JVMCIArray array);
@@ -356,6 +367,7 @@ class JVMCIEnv : public ResourceObj {
356367
DO_THROW(InvalidInstalledCodeException)
357368
DO_THROW(UnsatisfiedLinkError)
358369
DO_THROW(UnsupportedOperationException)
370+
DO_THROW(OutOfMemoryError)
359371
DO_THROW(ClassNotFoundException)
360372

361373
#undef DO_THROW

src/hotspot/share/jvmci/jvmciJavaClasses.hpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,9 @@
244244
start_class(InternalError, java_lang_InternalError) \
245245
jvmci_constructor(InternalError, "(Ljava/lang/String;)V") \
246246
end_class \
247+
start_class(OutOfMemoryError, java_lang_OutOfMemoryError) \
248+
jvmci_constructor(OutOfMemoryError, "(Ljava/lang/String;)V") \
249+
end_class \
247250
start_class(ClassNotFoundException, java_lang_ClassNotFoundException) \
248251
jvmci_constructor(ClassNotFoundException, "(Ljava/lang/String;)V") \
249252
end_class \

0 commit comments

Comments
 (0)