Discussion:
RFR: 8208686: [AOT] JVMTI ResourceExhausted event repeated for same allocation
Daniel D. Daugherty
2018-09-25 15:11:18 UTC
Permalink
Adding serviceability-***@... since this is JVM/TI...

Dan
A major design point of Graal is to treat allocations as non-side effecting to give more freedom to the optimizer by reducing the number of distinct FrameStates that need to be managed. When failing an allocation, Graal will deoptimize to the last side effecting instruction before the allocation. This mean the VM code for heap allocation will potentially be executed twice, once from Graal compiled code and then again in the interpreter. While this is perfectly fine according to the JVM specification, it can cause confusing behavior for JVMTI based tools. They will receive 2 ResourceExhausted events for a single allocation. Furthermore, the first ResourceExhausted event (on the Graal allocation slow path) might denote a bytecode instruction that performs no allocation, making it hard to debug the memory failure.
The proposed solution is to add an extra set of JVMCI VM runtime calls for allocation. These entry points will attempt the allocation and upon failure,
skip side-effects such as posting JVMTI events or handling -XX:OnOutOfMemoryError. The compiled code using these entry points is expected deoptmize on null.
* Returning null instead of throwing an exception on failure.
* Adding a `bool null_on_fail` argument to all relevant methods.
* Adding extra null checking where necessary after each call to these methods when `null_on_fail == true`.
This represents a significant number of changes.
Instead, the proposed solution introduces a new _in_retryable_allocation thread-local. This way, only the entry points and allocation routines that raise an exception need to be modified. Failure is communicated back to the new entry points by throwing a special pre-allocated OOME object (i.e., Universe::out_of_memory_error_retry()) which must not propagate back to Java code. Use of this object is not strictly necessary; it is introduced to highlight/document the special allocation mode.
The proposed solution is at http://cr.openjdk.java.net/~dnsimon/8208686.
THE JBS bug is: https://bugs.openjdk.java.net/browse/JDK-8208686
-Doug
Vladimir Kozlov
2018-09-28 17:51:35 UTC
Permalink
To let you know, me and Tom R. did review these changes and agreed that it is the least intrusive
changes for Hotspot shared code.

Thanks,
Vladimir
Dan
A major design point of Graal is to treat allocations as non-side effecting to give more freedom
to the optimizer by reducing the number of distinct FrameStates that need to be managed. When
failing an allocation, Graal will deoptimize to the last side effecting instruction before the
allocation. This mean the VM code for heap allocation will potentially be executed twice, once
from Graal compiled code and then again in the interpreter. While this is perfectly fine according
to the JVM specification, it can cause confusing behavior for JVMTI based tools. They will receive
2 ResourceExhausted events for a single allocation. Furthermore, the first ResourceExhausted event
(on the Graal allocation slow path) might denote a bytecode instruction that performs no
allocation, making it hard to debug the memory failure.
The proposed solution is to add an extra set of JVMCI VM runtime calls for allocation. These entry
points will attempt the allocation and upon failure,
skip side-effects such as posting JVMTI events or handling -XX:OnOutOfMemoryError. The compiled
code using these entry points is expected deoptmize on null.
The path from these new entry points to where allocation can fail goes through quite a bit of VM
* Returning null instead of throwing an exception on failure.
* Adding a `bool null_on_fail` argument to all relevant methods.
* Adding extra null checking where necessary after each call to these methods when `null_on_fail
== true`.
This represents a significant number of changes.
Instead, the proposed solution introduces a new _in_retryable_allocation thread-local. This way,
only the entry points and allocation routines that raise an exception need to be modified. Failure
is communicated back to the new entry points by throwing a special pre-allocated OOME object
(i.e., Universe::out_of_memory_error_retry()) which must not propagate back to Java code. Use of
this object is not strictly necessary; it is introduced to highlight/document the special
allocation mode.
The proposed solution is at http://cr.openjdk.java.net/~dnsimon/8208686.
THE JBS bug is: https://bugs.openjdk.java.net/browse/JDK-8208686
-Doug
Doug Simon
2018-10-02 08:11:24 UTC
Permalink
It would be great to get some input from the non-compilers teams on this RFR.

-Doug
To let you know, me and Tom R. did review these changes and agreed that it is the least intrusive changes for Hotspot shared code.
Thanks,
Vladimir
Dan
A major design point of Graal is to treat allocations as non-side effecting to give more freedom to the optimizer by reducing the number of distinct FrameStates that need to be managed. When failing an allocation, Graal will deoptimize to the last side effecting instruction before the allocation. This mean the VM code for heap allocation will potentially be executed twice, once from Graal compiled code and then again in the interpreter. While this is perfectly fine according to the JVM specification, it can cause confusing behavior for JVMTI based tools. They will receive 2 ResourceExhausted events for a single allocation. Furthermore, the first ResourceExhausted event (on the Graal allocation slow path) might denote a bytecode instruction that performs no allocation, making it hard to debug the memory failure.
The proposed solution is to add an extra set of JVMCI VM runtime calls for allocation. These entry points will attempt the allocation and upon failure,
skip side-effects such as posting JVMTI events or handling -XX:OnOutOfMemoryError. The compiled code using these entry points is expected deoptmize on null.
* Returning null instead of throwing an exception on failure.
* Adding a `bool null_on_fail` argument to all relevant methods.
* Adding extra null checking where necessary after each call to these methods when `null_on_fail == true`.
This represents a significant number of changes.
Instead, the proposed solution introduces a new _in_retryable_allocation thread-local. This way, only the entry points and allocation routines that raise an exception need to be modified. Failure is communicated back to the new entry points by throwing a special pre-allocated OOME object (i.e., Universe::out_of_memory_error_retry()) which must not propagate back to Java code. Use of this object is not strictly necessary; it is introduced to highlight/document the special allocation mode.
The proposed solution is at http://cr.openjdk.java.net/~dnsimon/8208686.
THE JBS bug is: https://bugs.openjdk.java.net/browse/JDK-8208686
-Doug
s***@oracle.com
2018-10-03 01:14:18 UTC
Permalink
Hi Doug,

The JVMTI related part looks good to me.
Thank you for fixing it!

Thanks,
Serguei
Post by Doug Simon
It would be great to get some input from the non-compilers teams on this RFR.
-Doug
To let you know, me and Tom R. did review these changes and agreed that it is the least intrusive changes for Hotspot shared code.
Thanks,
Vladimir
Dan
A major design point of Graal is to treat allocations as non-side effecting to give more freedom to the optimizer by reducing the number of distinct FrameStates that need to be managed. When failing an allocation, Graal will deoptimize to the last side effecting instruction before the allocation. This mean the VM code for heap allocation will potentially be executed twice, once from Graal compiled code and then again in the interpreter. While this is perfectly fine according to the JVM specification, it can cause confusing behavior for JVMTI based tools. They will receive 2 ResourceExhausted events for a single allocation. Furthermore, the first ResourceExhausted event (on the Graal allocation slow path) might denote a bytecode instruction that performs no allocation, making it hard to debug the memory failure.
The proposed solution is to add an extra set of JVMCI VM runtime calls for allocation. These entry points will attempt the allocation and upon failure,
skip side-effects such as posting JVMTI events or handling -XX:OnOutOfMemoryError. The compiled code using these entry points is expected deoptmize on null.
* Returning null instead of throwing an exception on failure.
* Adding a `bool null_on_fail` argument to all relevant methods.
* Adding extra null checking where necessary after each call to these methods when `null_on_fail == true`.
This represents a significant number of changes.
Instead, the proposed solution introduces a new _in_retryable_allocation thread-local. This way, only the entry points and allocation routines that raise an exception need to be modified. Failure is communicated back to the new entry points by throwing a special pre-allocated OOME object (i.e., Universe::out_of_memory_error_retry()) which must not propagate back to Java code. Use of this object is not strictly necessary; it is introduced to highlight/document the special allocation mode.
The proposed solution is at http://cr.openjdk.java.net/~dnsimon/8208686.
THE JBS bug is: https://bugs.openjdk.java.net/browse/JDK-8208686
-Doug
Doug Simon
2018-10-03 22:20:48 UTC
Permalink
Thanks for the reviews Serguei and Vladimir.

Unless I hear objections in the next 24 hours, I'll push this webrev.

-Doug
Post by s***@oracle.com
Hi Doug,
The JVMTI related part looks good to me.
Thank you for fixing it!
Thanks,
Serguei
Post by Doug Simon
It would be great to get some input from the non-compilers teams on this RFR.
-Doug
To let you know, me and Tom R. did review these changes and agreed that it is the least intrusive changes for Hotspot shared code.
Thanks,
Vladimir
Dan
A major design point of Graal is to treat allocations as non-side effecting to give more freedom to the optimizer by reducing the number of distinct FrameStates that need to be managed. When failing an allocation, Graal will deoptimize to the last side effecting instruction before the allocation. This mean the VM code for heap allocation will potentially be executed twice, once from Graal compiled code and then again in the interpreter. While this is perfectly fine according to the JVM specification, it can cause confusing behavior for JVMTI based tools. They will receive 2 ResourceExhausted events for a single allocation. Furthermore, the first ResourceExhausted event (on the Graal allocation slow path) might denote a bytecode instruction that performs no allocation, making it hard to debug the memory failure.
The proposed solution is to add an extra set of JVMCI VM runtime calls for allocation. These entry points will attempt the allocation and upon failure,
skip side-effects such as posting JVMTI events or handling -XX:OnOutOfMemoryError. The compiled code using these entry points is expected deoptmize on null.
* Returning null instead of throwing an exception on failure.
* Adding a `bool null_on_fail` argument to all relevant methods.
* Adding extra null checking where necessary after each call to these methods when `null_on_fail == true`.
This represents a significant number of changes.
Instead, the proposed solution introduces a new _in_retryable_allocation thread-local. This way, only the entry points and allocation routines that raise an exception need to be modified. Failure is communicated back to the new entry points by throwing a special pre-allocated OOME object (i.e., Universe::out_of_memory_error_retry()) which must not propagate back to Java code. Use of this object is not strictly necessary; it is introduced to highlight/document the special allocation mode.
The proposed solution is at http://cr.openjdk.java.net/~dnsimon/8208686.
THE JBS bug is: https://bugs.openjdk.java.net/browse/JDK-8208686
-Doug
Doug Simon
2018-10-05 15:22:18 UTC
Permalink
Hi,

Testing found a bug in the original webrev. Namely, when clearing out a pending exception and returning null in the JVMCI ..._or_null stubs, the JavaThread::_vm_result field was not being set to NULL. I've addressed this in v2 of the webrev:

Relative diff for bug fix:

-----------------------------------------------------------------------------------------------
-// Manages a scope in which a failed heap allocation will throw an exception.
-// The pending exception is cleared when leaving the scope.
+// Manages a scope for a JVMCI runtime call that attempts a heap allocation.
+// If there is a pending exception 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 exception 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 {
private:
JavaThread* _thread;
public:
RetryableAllocationMark(JavaThread* thread, bool activate) {
if (activate) {
- assert(thread->in_retryable_allocation(), "retryable allocation scope is non-reentrant");
+ assert(!thread->in_retryable_allocation(), "retryable allocation scope is non-reentrant");
_thread = thread;
_thread->set_in_retryable_allocation(true);
} else {
@@ -136,6 +141,7 @@
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(NULL);
}
}
}
-----------------------------------------------------------------------------------------------

I also took the opportunity to factor out negative array length checking:

-----------------------------------------------------------------------------------------------
diff -r 4d36f5998a8b src/hotspot/share/oops/arrayKlass.cpp
--- a/src/hotspot/share/oops/arrayKlass.cpp Fri Oct 05 17:04:06 2018 +0200
+++ b/src/hotspot/share/oops/arrayKlass.cpp Fri Oct 05 17:17:17 2018 +0200
@@ -130,9 +130,6 @@
}

objArrayOop ArrayKlass::allocate_arrayArray(int n, int length, TRAPS) {
- if (length < 0) {
- THROW_MSG_0(vmSymbols::java_lang_NegativeArraySizeException(), err_msg("%d", length));
- }
check_array_allocation_length(length, arrayOopDesc::max_array_length(T_ARRAY), CHECK_0);
int size = objArrayOopDesc::object_size(length);
Klass* k = array_klass(n+dimension(), CHECK_0);
diff -r 4d36f5998a8b src/hotspot/share/oops/instanceKlass.cpp
--- a/src/hotspot/share/oops/instanceKlass.cpp Fri Oct 05 17:04:06 2018 +0200
+++ b/src/hotspot/share/oops/instanceKlass.cpp Fri Oct 05 17:17:17 2018 +0200
@@ -1201,9 +1201,6 @@
}

objArrayOop InstanceKlass::allocate_objArray(int n, int length, TRAPS) {
- if (length < 0) {
- THROW_MSG_0(vmSymbols::java_lang_NegativeArraySizeException(), err_msg("%d", length));
- }
check_array_allocation_length(length, arrayOopDesc::max_array_length(T_OBJECT), CHECK_NULL);
int size = objArrayOopDesc::object_size(length);
Klass* ak = array_klass(n, CHECK_NULL);
diff -r 4d36f5998a8b src/hotspot/share/oops/klass.cpp
--- a/src/hotspot/share/oops/klass.cpp Fri Oct 05 17:04:06 2018 +0200
+++ b/src/hotspot/share/oops/klass.cpp Fri Oct 05 17:17:17 2018 +0200
@@ -620,6 +620,8 @@
} else {
THROW_OOP(Universe::out_of_memory_error_retry());
}
+ } else if (length < 0) {
+ THROW_MSG(vmSymbols::java_lang_NegativeArraySizeException(), err_msg("%d", length));
}
}

diff -r 4d36f5998a8b src/hotspot/share/oops/klass.hpp
--- a/src/hotspot/share/oops/klass.hpp Fri Oct 05 17:04:06 2018 +0200
+++ b/src/hotspot/share/oops/klass.hpp Fri Oct 05 17:17:17 2018 +0200
@@ -514,7 +514,7 @@
virtual Klass* array_klass_impl(bool or_null, int rank, TRAPS);
virtual Klass* array_klass_impl(bool or_null, TRAPS);

- // Error handling when length > max_length
+ // Error handling when length > max_length or length < 0
static void check_array_allocation_length(int length, int max_length, TRAPS);

void set_vtable_length(int len) { _vtable_len= len; }
diff -r 4d36f5998a8b src/hotspot/share/oops/objArrayKlass.cpp
--- a/src/hotspot/share/oops/objArrayKlass.cpp Fri Oct 05 17:04:06 2018 +0200
+++ b/src/hotspot/share/oops/objArrayKlass.cpp Fri Oct 05 17:17:17 2018 +0200
@@ -170,14 +170,10 @@
}

objArrayOop ObjArrayKlass::allocate(int length, TRAPS) {
- if (length >= 0) {
- check_array_allocation_length(length, arrayOopDesc::max_array_length(T_OBJECT), CHECK_0);
- int size = objArrayOopDesc::object_size(length);
- return (objArrayOop)Universe::heap()->array_allocate(this, size, length,
- /* do_zero */ true, THREAD);
- } else {
- THROW_MSG_0(vmSymbols::java_lang_NegativeArraySizeException(), err_msg("%d", length));
- }
+ check_array_allocation_length(length, arrayOopDesc::max_array_length(T_OBJECT), CHECK_0);
+ int size = objArrayOopDesc::object_size(length);
+ return (objArrayOop)Universe::heap()->array_allocate(this, size, length,
+ /* do_zero */ true, THREAD);
}

static int multi_alloc_counter = 0;
diff -r 4d36f5998a8b src/hotspot/share/oops/typeArrayKlass.cpp
--- a/src/hotspot/share/oops/typeArrayKlass.cpp Fri Oct 05 17:04:06 2018 +0200
+++ b/src/hotspot/share/oops/typeArrayKlass.cpp Fri Oct 05 17:17:17 2018 +0200
@@ -99,14 +99,10 @@

typeArrayOop TypeArrayKlass::allocate_common(int length, bool do_zero, TRAPS) {
assert(log2_element_size() >= 0, "bad scale");
- if (length >= 0) {
- check_array_allocation_length(length, max_length(), CHECK_NULL);
- size_t size = typeArrayOopDesc::object_size(layout_helper(), length);
- return (typeArrayOop)Universe::heap()->array_allocate(this, (int)size, length,
- do_zero, CHECK_NULL);
- } else {
- THROW_MSG_0(vmSymbols::java_lang_NegativeArraySizeException(), err_msg("%d", length));
- }
+ check_array_allocation_length(length, max_length(), CHECK_NULL);
+ size_t size = typeArrayOopDesc::object_size(layout_helper(), length);
+ return (typeArrayOop)Universe::heap()->array_allocate(this, (int)size, length,
+ do_zero, CHECK_NULL);
}

oop TypeArrayKlass::multi_allocate(int rank, jint* last_size, TRAPS) {
-----------------------------------------------------------------------------------------------

Please confirm review these new changes:

http://cr.openjdk.java.net/~dnsimon/8208686v2

-Doug
Post by Doug Simon
Thanks for the reviews Serguei and Vladimir.
Unless I hear objections in the next 24 hours, I'll push this webrev.
-Doug
Post by s***@oracle.com
Hi Doug,
The JVMTI related part looks good to me.
Thank you for fixing it!
Thanks,
Serguei
Post by Doug Simon
It would be great to get some input from the non-compilers teams on this RFR.
-Doug
To let you know, me and Tom R. did review these changes and agreed that it is the least intrusive changes for Hotspot shared code.
Thanks,
Vladimir
Dan
A major design point of Graal is to treat allocations as non-side effecting to give more freedom to the optimizer by reducing the number of distinct FrameStates that need to be managed. When failing an allocation, Graal will deoptimize to the last side effecting instruction before the allocation. This mean the VM code for heap allocation will potentially be executed twice, once from Graal compiled code and then again in the interpreter. While this is perfectly fine according to the JVM specification, it can cause confusing behavior for JVMTI based tools. They will receive 2 ResourceExhausted events for a single allocation. Furthermore, the first ResourceExhausted event (on the Graal allocation slow path) might denote a bytecode instruction that performs no allocation, making it hard to debug the memory failure.
The proposed solution is to add an extra set of JVMCI VM runtime calls for allocation. These entry points will attempt the allocation and upon failure,
skip side-effects such as posting JVMTI events or handling -XX:OnOutOfMemoryError. The compiled code using these entry points is expected deoptmize on null.
* Returning null instead of throwing an exception on failure.
* Adding a `bool null_on_fail` argument to all relevant methods.
* Adding extra null checking where necessary after each call to these methods when `null_on_fail == true`.
This represents a significant number of changes.
Instead, the proposed solution introduces a new _in_retryable_allocation thread-local. This way, only the entry points and allocation routines that raise an exception need to be modified. Failure is communicated back to the new entry points by throwing a special pre-allocated OOME object (i.e., Universe::out_of_memory_error_retry()) which must not propagate back to Java code. Use of this object is not strictly necessary; it is introduced to highlight/document the special allocation mode.
The proposed solution is at http://cr.openjdk.java.net/~dnsimon/8208686.
THE JBS bug is: https://bugs.openjdk.java.net/browse/JDK-8208686
-Doug
Vladimir Kozlov
2018-10-05 16:05:54 UTC
Permalink
Looks good.

Thanks,
Vladimir
Post by Doug Simon
Hi,
-----------------------------------------------------------------------------------------------
-// Manages a scope in which a failed heap allocation will throw an exception.
-// The pending exception is cleared when leaving the scope.
+// Manages a scope for a JVMCI runtime call that attempts a heap allocation.
+// If there is a pending exception upon closing the scope and the runtime
+// call is of the variety where allocation failure returns NULL without an
+// 1. The pending exception 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 {
JavaThread* _thread;
RetryableAllocationMark(JavaThread* thread, bool activate) {
if (activate) {
- assert(thread->in_retryable_allocation(), "retryable allocation scope is non-reentrant");
+ assert(!thread->in_retryable_allocation(), "retryable allocation scope is non-reentrant");
_thread = thread;
_thread->set_in_retryable_allocation(true);
} else {
@@ -136,6 +141,7 @@
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(NULL);
}
}
}
-----------------------------------------------------------------------------------------------
-----------------------------------------------------------------------------------------------
diff -r 4d36f5998a8b src/hotspot/share/oops/arrayKlass.cpp
--- a/src/hotspot/share/oops/arrayKlass.cpp Fri Oct 05 17:04:06 2018 +0200
+++ b/src/hotspot/share/oops/arrayKlass.cpp Fri Oct 05 17:17:17 2018 +0200
@@ -130,9 +130,6 @@
}
objArrayOop ArrayKlass::allocate_arrayArray(int n, int length, TRAPS) {
- if (length < 0) {
- THROW_MSG_0(vmSymbols::java_lang_NegativeArraySizeException(), err_msg("%d", length));
- }
check_array_allocation_length(length, arrayOopDesc::max_array_length(T_ARRAY), CHECK_0);
int size = objArrayOopDesc::object_size(length);
Klass* k = array_klass(n+dimension(), CHECK_0);
diff -r 4d36f5998a8b src/hotspot/share/oops/instanceKlass.cpp
--- a/src/hotspot/share/oops/instanceKlass.cpp Fri Oct 05 17:04:06 2018 +0200
+++ b/src/hotspot/share/oops/instanceKlass.cpp Fri Oct 05 17:17:17 2018 +0200
@@ -1201,9 +1201,6 @@
}
objArrayOop InstanceKlass::allocate_objArray(int n, int length, TRAPS) {
- if (length < 0) {
- THROW_MSG_0(vmSymbols::java_lang_NegativeArraySizeException(), err_msg("%d", length));
- }
check_array_allocation_length(length, arrayOopDesc::max_array_length(T_OBJECT), CHECK_NULL);
int size = objArrayOopDesc::object_size(length);
Klass* ak = array_klass(n, CHECK_NULL);
diff -r 4d36f5998a8b src/hotspot/share/oops/klass.cpp
--- a/src/hotspot/share/oops/klass.cpp Fri Oct 05 17:04:06 2018 +0200
+++ b/src/hotspot/share/oops/klass.cpp Fri Oct 05 17:17:17 2018 +0200
@@ -620,6 +620,8 @@
} else {
THROW_OOP(Universe::out_of_memory_error_retry());
}
+ } else if (length < 0) {
+ THROW_MSG(vmSymbols::java_lang_NegativeArraySizeException(), err_msg("%d", length));
}
}
diff -r 4d36f5998a8b src/hotspot/share/oops/klass.hpp
--- a/src/hotspot/share/oops/klass.hpp Fri Oct 05 17:04:06 2018 +0200
+++ b/src/hotspot/share/oops/klass.hpp Fri Oct 05 17:17:17 2018 +0200
@@ -514,7 +514,7 @@
virtual Klass* array_klass_impl(bool or_null, int rank, TRAPS);
virtual Klass* array_klass_impl(bool or_null, TRAPS);
- // Error handling when length > max_length
+ // Error handling when length > max_length or length < 0
static void check_array_allocation_length(int length, int max_length, TRAPS);
void set_vtable_length(int len) { _vtable_len= len; }
diff -r 4d36f5998a8b src/hotspot/share/oops/objArrayKlass.cpp
--- a/src/hotspot/share/oops/objArrayKlass.cpp Fri Oct 05 17:04:06 2018 +0200
+++ b/src/hotspot/share/oops/objArrayKlass.cpp Fri Oct 05 17:17:17 2018 +0200
@@ -170,14 +170,10 @@
}
objArrayOop ObjArrayKlass::allocate(int length, TRAPS) {
- if (length >= 0) {
- check_array_allocation_length(length, arrayOopDesc::max_array_length(T_OBJECT), CHECK_0);
- int size = objArrayOopDesc::object_size(length);
- return (objArrayOop)Universe::heap()->array_allocate(this, size, length,
- /* do_zero */ true, THREAD);
- } else {
- THROW_MSG_0(vmSymbols::java_lang_NegativeArraySizeException(), err_msg("%d", length));
- }
+ check_array_allocation_length(length, arrayOopDesc::max_array_length(T_OBJECT), CHECK_0);
+ int size = objArrayOopDesc::object_size(length);
+ return (objArrayOop)Universe::heap()->array_allocate(this, size, length,
+ /* do_zero */ true, THREAD);
}
static int multi_alloc_counter = 0;
diff -r 4d36f5998a8b src/hotspot/share/oops/typeArrayKlass.cpp
--- a/src/hotspot/share/oops/typeArrayKlass.cpp Fri Oct 05 17:04:06 2018 +0200
+++ b/src/hotspot/share/oops/typeArrayKlass.cpp Fri Oct 05 17:17:17 2018 +0200
@@ -99,14 +99,10 @@
typeArrayOop TypeArrayKlass::allocate_common(int length, bool do_zero, TRAPS) {
assert(log2_element_size() >= 0, "bad scale");
- if (length >= 0) {
- check_array_allocation_length(length, max_length(), CHECK_NULL);
- size_t size = typeArrayOopDesc::object_size(layout_helper(), length);
- return (typeArrayOop)Universe::heap()->array_allocate(this, (int)size, length,
- do_zero, CHECK_NULL);
- } else {
- THROW_MSG_0(vmSymbols::java_lang_NegativeArraySizeException(), err_msg("%d", length));
- }
+ check_array_allocation_length(length, max_length(), CHECK_NULL);
+ size_t size = typeArrayOopDesc::object_size(layout_helper(), length);
+ return (typeArrayOop)Universe::heap()->array_allocate(this, (int)size, length,
+ do_zero, CHECK_NULL);
}
oop TypeArrayKlass::multi_allocate(int rank, jint* last_size, TRAPS) {
-----------------------------------------------------------------------------------------------
http://cr.openjdk.java.net/~dnsimon/8208686v2
-Doug
Post by Doug Simon
Thanks for the reviews Serguei and Vladimir.
Unless I hear objections in the next 24 hours, I'll push this webrev.
-Doug
Post by s***@oracle.com
Hi Doug,
The JVMTI related part looks good to me.
Thank you for fixing it!
Thanks,
Serguei
Post by Doug Simon
It would be great to get some input from the non-compilers teams on this RFR.
-Doug
To let you know, me and Tom R. did review these changes and agreed that it is the least intrusive changes for Hotspot shared code.
Thanks,
Vladimir
Dan
A major design point of Graal is to treat allocations as non-side effecting to give more freedom to the optimizer by reducing the number of distinct FrameStates that need to be managed. When failing an allocation, Graal will deoptimize to the last side effecting instruction before the allocation. This mean the VM code for heap allocation will potentially be executed twice, once from Graal compiled code and then again in the interpreter. While this is perfectly fine according to the JVM specification, it can cause confusing behavior for JVMTI based tools. They will receive 2 ResourceExhausted events for a single allocation. Furthermore, the first ResourceExhausted event (on the Graal allocation slow path) might denote a bytecode instruction that performs no allocation, making it hard to debug the memory failure.
The proposed solution is to add an extra set of JVMCI VM runtime calls for allocation. These entry points will attempt the allocation and upon failure,
skip side-effects such as posting JVMTI events or handling -XX:OnOutOfMemoryError. The compiled code using these entry points is expected deoptmize on null.
* Returning null instead of throwing an exception on failure.
* Adding a `bool null_on_fail` argument to all relevant methods.
* Adding extra null checking where necessary after each call to these methods when `null_on_fail == true`.
This represents a significant number of changes.
Instead, the proposed solution introduces a new _in_retryable_allocation thread-local. This way, only the entry points and allocation routines that raise an exception need to be modified. Failure is communicated back to the new entry points by throwing a special pre-allocated OOME object (i.e., Universe::out_of_memory_error_retry()) which must not propagate back to Java code. Use of this object is not strictly necessary; it is introduced to highlight/document the special allocation mode.
The proposed solution is at http://cr.openjdk.java.net/~dnsimon/8208686.
THE JBS bug is: https://bugs.openjdk.java.net/browse/JDK-8208686
-Doug
Doug Simon
2018-10-05 17:58:39 UTC
Permalink
Thanks for re-reviewing.

-Doug
Post by Vladimir Kozlov
Looks good.
Thanks,
Vladimir
Post by Doug Simon
Hi,
-----------------------------------------------------------------------------------------------
-// Manages a scope in which a failed heap allocation will throw an exception.
-// The pending exception is cleared when leaving the scope.
+// Manages a scope for a JVMCI runtime call that attempts a heap allocation.
+// If there is a pending exception upon closing the scope and the runtime
+// call is of the variety where allocation failure returns NULL without an
+// 1. The pending exception 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 {
JavaThread* _thread;
RetryableAllocationMark(JavaThread* thread, bool activate) {
if (activate) {
- assert(thread->in_retryable_allocation(), "retryable allocation scope is non-reentrant");
+ assert(!thread->in_retryable_allocation(), "retryable allocation scope is non-reentrant");
_thread = thread;
_thread->set_in_retryable_allocation(true);
} else {
@@ -136,6 +141,7 @@
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(NULL);
}
}
}
-----------------------------------------------------------------------------------------------
-----------------------------------------------------------------------------------------------
diff -r 4d36f5998a8b src/hotspot/share/oops/arrayKlass.cpp
--- a/src/hotspot/share/oops/arrayKlass.cpp Fri Oct 05 17:04:06 2018 +0200
+++ b/src/hotspot/share/oops/arrayKlass.cpp Fri Oct 05 17:17:17 2018 +0200
@@ -130,9 +130,6 @@
}
objArrayOop ArrayKlass::allocate_arrayArray(int n, int length, TRAPS) {
- if (length < 0) {
- THROW_MSG_0(vmSymbols::java_lang_NegativeArraySizeException(), err_msg("%d", length));
- }
check_array_allocation_length(length, arrayOopDesc::max_array_length(T_ARRAY), CHECK_0);
int size = objArrayOopDesc::object_size(length);
Klass* k = array_klass(n+dimension(), CHECK_0);
diff -r 4d36f5998a8b src/hotspot/share/oops/instanceKlass.cpp
--- a/src/hotspot/share/oops/instanceKlass.cpp Fri Oct 05 17:04:06 2018 +0200
+++ b/src/hotspot/share/oops/instanceKlass.cpp Fri Oct 05 17:17:17 2018 +0200
@@ -1201,9 +1201,6 @@
}
objArrayOop InstanceKlass::allocate_objArray(int n, int length, TRAPS) {
- if (length < 0) {
- THROW_MSG_0(vmSymbols::java_lang_NegativeArraySizeException(), err_msg("%d", length));
- }
check_array_allocation_length(length, arrayOopDesc::max_array_length(T_OBJECT), CHECK_NULL);
int size = objArrayOopDesc::object_size(length);
Klass* ak = array_klass(n, CHECK_NULL);
diff -r 4d36f5998a8b src/hotspot/share/oops/klass.cpp
--- a/src/hotspot/share/oops/klass.cpp Fri Oct 05 17:04:06 2018 +0200
+++ b/src/hotspot/share/oops/klass.cpp Fri Oct 05 17:17:17 2018 +0200
@@ -620,6 +620,8 @@
} else {
THROW_OOP(Universe::out_of_memory_error_retry());
}
+ } else if (length < 0) {
+ THROW_MSG(vmSymbols::java_lang_NegativeArraySizeException(), err_msg("%d", length));
}
}
diff -r 4d36f5998a8b src/hotspot/share/oops/klass.hpp
--- a/src/hotspot/share/oops/klass.hpp Fri Oct 05 17:04:06 2018 +0200
+++ b/src/hotspot/share/oops/klass.hpp Fri Oct 05 17:17:17 2018 +0200
@@ -514,7 +514,7 @@
virtual Klass* array_klass_impl(bool or_null, int rank, TRAPS);
virtual Klass* array_klass_impl(bool or_null, TRAPS);
- // Error handling when length > max_length
+ // Error handling when length > max_length or length < 0
static void check_array_allocation_length(int length, int max_length, TRAPS);
void set_vtable_length(int len) { _vtable_len= len; }
diff -r 4d36f5998a8b src/hotspot/share/oops/objArrayKlass.cpp
--- a/src/hotspot/share/oops/objArrayKlass.cpp Fri Oct 05 17:04:06 2018 +0200
+++ b/src/hotspot/share/oops/objArrayKlass.cpp Fri Oct 05 17:17:17 2018 +0200
@@ -170,14 +170,10 @@
}
objArrayOop ObjArrayKlass::allocate(int length, TRAPS) {
- if (length >= 0) {
- check_array_allocation_length(length, arrayOopDesc::max_array_length(T_OBJECT), CHECK_0);
- int size = objArrayOopDesc::object_size(length);
- return (objArrayOop)Universe::heap()->array_allocate(this, size, length,
- /* do_zero */ true, THREAD);
- } else {
- THROW_MSG_0(vmSymbols::java_lang_NegativeArraySizeException(), err_msg("%d", length));
- }
+ check_array_allocation_length(length, arrayOopDesc::max_array_length(T_OBJECT), CHECK_0);
+ int size = objArrayOopDesc::object_size(length);
+ return (objArrayOop)Universe::heap()->array_allocate(this, size, length,
+ /* do_zero */ true, THREAD);
}
static int multi_alloc_counter = 0;
diff -r 4d36f5998a8b src/hotspot/share/oops/typeArrayKlass.cpp
--- a/src/hotspot/share/oops/typeArrayKlass.cpp Fri Oct 05 17:04:06 2018 +0200
+++ b/src/hotspot/share/oops/typeArrayKlass.cpp Fri Oct 05 17:17:17 2018 +0200
@@ -99,14 +99,10 @@
typeArrayOop TypeArrayKlass::allocate_common(int length, bool do_zero, TRAPS) {
assert(log2_element_size() >= 0, "bad scale");
- if (length >= 0) {
- check_array_allocation_length(length, max_length(), CHECK_NULL);
- size_t size = typeArrayOopDesc::object_size(layout_helper(), length);
- return (typeArrayOop)Universe::heap()->array_allocate(this, (int)size, length,
- do_zero, CHECK_NULL);
- } else {
- THROW_MSG_0(vmSymbols::java_lang_NegativeArraySizeException(), err_msg("%d", length));
- }
+ check_array_allocation_length(length, max_length(), CHECK_NULL);
+ size_t size = typeArrayOopDesc::object_size(layout_helper(), length);
+ return (typeArrayOop)Universe::heap()->array_allocate(this, (int)size, length,
+ do_zero, CHECK_NULL);
}
oop TypeArrayKlass::multi_allocate(int rank, jint* last_size, TRAPS) {
-----------------------------------------------------------------------------------------------
http://cr.openjdk.java.net/~dnsimon/8208686v2
-Doug
Post by Doug Simon
Thanks for the reviews Serguei and Vladimir.
Unless I hear objections in the next 24 hours, I'll push this webrev.
-Doug
Post by s***@oracle.com
Hi Doug,
The JVMTI related part looks good to me.
Thank you for fixing it!
Thanks,
Serguei
Post by Doug Simon
It would be great to get some input from the non-compilers teams on this RFR.
-Doug
To let you know, me and Tom R. did review these changes and agreed that it is the least intrusive changes for Hotspot shared code.
Thanks,
Vladimir
Dan
A major design point of Graal is to treat allocations as non-side effecting to give more freedom to the optimizer by reducing the number of distinct FrameStates that need to be managed. When failing an allocation, Graal will deoptimize to the last side effecting instruction before the allocation. This mean the VM code for heap allocation will potentially be executed twice, once from Graal compiled code and then again in the interpreter. While this is perfectly fine according to the JVM specification, it can cause confusing behavior for JVMTI based tools. They will receive 2 ResourceExhausted events for a single allocation. Furthermore, the first ResourceExhausted event (on the Graal allocation slow path) might denote a bytecode instruction that performs no allocation, making it hard to debug the memory failure.
The proposed solution is to add an extra set of JVMCI VM runtime calls for allocation. These entry points will attempt the allocation and upon failure,
skip side-effects such as posting JVMTI events or handling -XX:OnOutOfMemoryError. The compiled code using these entry points is expected deoptmize on null.
* Returning null instead of throwing an exception on failure.
* Adding a `bool null_on_fail` argument to all relevant methods.
* Adding extra null checking where necessary after each call to these methods when `null_on_fail == true`.
This represents a significant number of changes.
Instead, the proposed solution introduces a new _in_retryable_allocation thread-local. This way, only the entry points and allocation routines that raise an exception need to be modified. Failure is communicated back to the new entry points by throwing a special pre-allocated OOME object (i.e., Universe::out_of_memory_error_retry()) which must not propagate back to Java code. Use of this object is not strictly necessary; it is introduced to highlight/document the special allocation mode.
The proposed solution is at http://cr.openjdk.java.net/~dnsimon/8208686.
THE JBS bug is: https://bugs.openjdk.java.net/browse/JDK-8208686
-Doug
s***@oracle.com
2018-10-05 19:56:12 UTC
Permalink
+1

Thanks,
Serguei
Post by Vladimir Kozlov
Looks good.
Thanks,
Vladimir
Post by Doug Simon
Hi,
Testing found a bug in the original webrev. Namely, when clearing out
a pending exception and returning null in the JVMCI ..._or_null
stubs, the JavaThread::_vm_result field was not being set to NULL.
-----------------------------------------------------------------------------------------------
-// Manages a scope in which a failed heap allocation will throw an exception.
-// The pending exception is cleared when leaving the scope.
+// Manages a scope for a JVMCI runtime call that attempts a heap allocation.
+// If there is a pending exception upon closing the scope and the runtime
+// call is of the variety where allocation failure returns NULL without an
+//   1. The pending exception 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 {
    JavaThread* _thread;
    RetryableAllocationMark(JavaThread* thread, bool activate) {
      if (activate) {
-      assert(thread->in_retryable_allocation(), "retryable
allocation scope is non-reentrant");
+      assert(!thread->in_retryable_allocation(), "retryable
allocation scope is non-reentrant");
        _thread = thread;
        _thread->set_in_retryable_allocation(true);
      } else {
@@ -136,6 +141,7 @@
            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(NULL);
        }
      }
    }
-----------------------------------------------------------------------------------------------
-----------------------------------------------------------------------------------------------
diff -r 4d36f5998a8b src/hotspot/share/oops/arrayKlass.cpp
--- a/src/hotspot/share/oops/arrayKlass.cpp    Fri Oct 05 17:04:06
2018 +0200
+++ b/src/hotspot/share/oops/arrayKlass.cpp    Fri Oct 05 17:17:17
2018 +0200
@@ -130,9 +130,6 @@
  }
    objArrayOop ArrayKlass::allocate_arrayArray(int n, int length,
TRAPS) {
-  if (length < 0) {
- THROW_MSG_0(vmSymbols::java_lang_NegativeArraySizeException(),
err_msg("%d", length));
-  }
    check_array_allocation_length(length,
arrayOopDesc::max_array_length(T_ARRAY), CHECK_0);
    int size = objArrayOopDesc::object_size(length);
    Klass* k = array_klass(n+dimension(), CHECK_0);
diff -r 4d36f5998a8b src/hotspot/share/oops/instanceKlass.cpp
--- a/src/hotspot/share/oops/instanceKlass.cpp    Fri Oct 05 17:04:06
2018 +0200
+++ b/src/hotspot/share/oops/instanceKlass.cpp    Fri Oct 05 17:17:17
2018 +0200
@@ -1201,9 +1201,6 @@
  }
    objArrayOop InstanceKlass::allocate_objArray(int n, int length,
TRAPS) {
-  if (length < 0)  {
- THROW_MSG_0(vmSymbols::java_lang_NegativeArraySizeException(),
err_msg("%d", length));
-  }
    check_array_allocation_length(length,
arrayOopDesc::max_array_length(T_OBJECT), CHECK_NULL);
    int size = objArrayOopDesc::object_size(length);
    Klass* ak = array_klass(n, CHECK_NULL);
diff -r 4d36f5998a8b src/hotspot/share/oops/klass.cpp
--- a/src/hotspot/share/oops/klass.cpp    Fri Oct 05 17:04:06 2018 +0200
+++ b/src/hotspot/share/oops/klass.cpp    Fri Oct 05 17:17:17 2018 +0200
@@ -620,6 +620,8 @@
      } else {
        THROW_OOP(Universe::out_of_memory_error_retry());
      }
+  } else if (length < 0) {
+ THROW_MSG(vmSymbols::java_lang_NegativeArraySizeException(),
err_msg("%d", length));
    }
  }
  diff -r 4d36f5998a8b src/hotspot/share/oops/klass.hpp
--- a/src/hotspot/share/oops/klass.hpp    Fri Oct 05 17:04:06 2018 +0200
+++ b/src/hotspot/share/oops/klass.hpp    Fri Oct 05 17:17:17 2018 +0200
@@ -514,7 +514,7 @@
    virtual Klass* array_klass_impl(bool or_null, int rank, TRAPS);
    virtual Klass* array_klass_impl(bool or_null, TRAPS);
  -  // Error handling when length > max_length
+  // Error handling when length > max_length or length < 0
    static void check_array_allocation_length(int length, int
max_length, TRAPS);
      void set_vtable_length(int len) { _vtable_len= len; }
diff -r 4d36f5998a8b src/hotspot/share/oops/objArrayKlass.cpp
--- a/src/hotspot/share/oops/objArrayKlass.cpp    Fri Oct 05 17:04:06
2018 +0200
+++ b/src/hotspot/share/oops/objArrayKlass.cpp    Fri Oct 05 17:17:17
2018 +0200
@@ -170,14 +170,10 @@
  }
    objArrayOop ObjArrayKlass::allocate(int length, TRAPS) {
-  if (length >= 0) {
-    check_array_allocation_length(length,
arrayOopDesc::max_array_length(T_OBJECT), CHECK_0);
-    int size = objArrayOopDesc::object_size(length);
-    return (objArrayOop)Universe::heap()->array_allocate(this, size,
length,
-                                                         /* do_zero
*/ true, THREAD);
-  } else {
- THROW_MSG_0(vmSymbols::java_lang_NegativeArraySizeException(),
err_msg("%d", length));
-  }
+  check_array_allocation_length(length,
arrayOopDesc::max_array_length(T_OBJECT), CHECK_0);
+  int size = objArrayOopDesc::object_size(length);
+  return (objArrayOop)Universe::heap()->array_allocate(this, size,
length,
+                                                       /* do_zero */
true, THREAD);
  }
    static int multi_alloc_counter = 0;
diff -r 4d36f5998a8b src/hotspot/share/oops/typeArrayKlass.cpp
--- a/src/hotspot/share/oops/typeArrayKlass.cpp    Fri Oct 05
17:04:06 2018 +0200
+++ b/src/hotspot/share/oops/typeArrayKlass.cpp    Fri Oct 05
17:17:17 2018 +0200
@@ -99,14 +99,10 @@
    typeArrayOop TypeArrayKlass::allocate_common(int length, bool
do_zero, TRAPS) {
    assert(log2_element_size() >= 0, "bad scale");
-  if (length >= 0) {
-    check_array_allocation_length(length, max_length(), CHECK_NULL);
-    size_t size = typeArrayOopDesc::object_size(layout_helper(),
length);
-    return (typeArrayOop)Universe::heap()->array_allocate(this,
(int)size, length,
- do_zero, CHECK_NULL);
-  } else {
- THROW_MSG_0(vmSymbols::java_lang_NegativeArraySizeException(),
err_msg("%d", length));
-  }
+  check_array_allocation_length(length, max_length(), CHECK_NULL);
+  size_t size = typeArrayOopDesc::object_size(layout_helper(), length);
+  return (typeArrayOop)Universe::heap()->array_allocate(this,
(int)size, length,
+ do_zero, CHECK_NULL);
  }
    oop TypeArrayKlass::multi_allocate(int rank, jint* last_size,
TRAPS) {
-----------------------------------------------------------------------------------------------
http://cr.openjdk.java.net/~dnsimon/8208686v2
-Doug
Post by Doug Simon
Thanks for the reviews Serguei and Vladimir.
Unless I hear objections in the next 24 hours, I'll push this webrev.
-Doug
Post by s***@oracle.com
Hi Doug,
The JVMTI related part looks good to me.
Thank you for fixing it!
Thanks,
Serguei
Post by Doug Simon
It would be great to get some input from the non-compilers teams on this RFR.
-Doug
On 28 Sep 2018, at 19:51, Vladimir Kozlov
To let you know, me and Tom R. did review these changes and
agreed that it is the least intrusive changes for Hotspot shared
code.
Thanks,
Vladimir
Dan
A major design point of Graal is to treat allocations as
non-side effecting to give more freedom to the optimizer by
reducing the number of distinct FrameStates that need to be
managed. When failing an allocation, Graal will deoptimize to
the last side effecting instruction before the allocation. This
mean the VM code for heap allocation will potentially be
executed twice, once from Graal compiled code and then again in
the interpreter. While this is perfectly fine according to the
JVM specification, it can cause confusing behavior for JVMTI
based tools. They will receive 2 ResourceExhausted events for a
single allocation. Furthermore, the first ResourceExhausted
event (on the Graal allocation slow path) might denote a
bytecode instruction that performs no allocation, making it
hard to debug the memory failure.
The proposed solution is to add an extra set of JVMCI VM
runtime calls for allocation. These entry points will attempt
the allocation and upon failure,
skip side-effects such as posting JVMTI events or handling
-XX:OnOutOfMemoryError. The compiled code using these entry
points is expected deoptmize on null.
The path from these new entry points to where allocation can
fail goes through quite a bit of VM code. One could modify all
* Returning null instead of throwing an exception on failure.
* Adding a `bool null_on_fail` argument to all relevant methods.
* Adding extra null checking where necessary after each call to
these methods when `null_on_fail == true`.
This represents a significant number of changes.
Instead, the proposed solution introduces a new
_in_retryable_allocation thread-local. This way, only the entry
points and allocation routines that raise an exception need to
be modified. Failure is communicated back to the new entry
points by throwing a special pre-allocated OOME object (i.e.,
Universe::out_of_memory_error_retry()) which must not propagate
back to Java code. Use of this object is not strictly
necessary; it is introduced to highlight/document the special
allocation mode.
The proposed solution is at
http://cr.openjdk.java.net/~dnsimon/8208686.
THE JBS bug is: https://bugs.openjdk.java.net/browse/JDK-8208686
-Doug
Tom Rodriguez
2018-10-05 17:27:08 UTC
Permalink
Looks good.

tom
Post by Doug Simon
Hi,
-----------------------------------------------------------------------------------------------
-// Manages a scope in which a failed heap allocation will throw an exception.
-// The pending exception is cleared when leaving the scope.
+// Manages a scope for a JVMCI runtime call that attempts a heap allocation.
+// If there is a pending exception upon closing the scope and the runtime
+// call is of the variety where allocation failure returns NULL without an
+// 1. The pending exception 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 {
JavaThread* _thread;
RetryableAllocationMark(JavaThread* thread, bool activate) {
if (activate) {
- assert(thread->in_retryable_allocation(), "retryable allocation scope is non-reentrant");
+ assert(!thread->in_retryable_allocation(), "retryable allocation scope is non-reentrant");
_thread = thread;
_thread->set_in_retryable_allocation(true);
} else {
@@ -136,6 +141,7 @@
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(NULL);
}
}
}
-----------------------------------------------------------------------------------------------
-----------------------------------------------------------------------------------------------
diff -r 4d36f5998a8b src/hotspot/share/oops/arrayKlass.cpp
--- a/src/hotspot/share/oops/arrayKlass.cpp Fri Oct 05 17:04:06 2018 +0200
+++ b/src/hotspot/share/oops/arrayKlass.cpp Fri Oct 05 17:17:17 2018 +0200
@@ -130,9 +130,6 @@
}
objArrayOop ArrayKlass::allocate_arrayArray(int n, int length, TRAPS) {
- if (length < 0) {
- THROW_MSG_0(vmSymbols::java_lang_NegativeArraySizeException(), err_msg("%d", length));
- }
check_array_allocation_length(length, arrayOopDesc::max_array_length(T_ARRAY), CHECK_0);
int size = objArrayOopDesc::object_size(length);
Klass* k = array_klass(n+dimension(), CHECK_0);
diff -r 4d36f5998a8b src/hotspot/share/oops/instanceKlass.cpp
--- a/src/hotspot/share/oops/instanceKlass.cpp Fri Oct 05 17:04:06 2018 +0200
+++ b/src/hotspot/share/oops/instanceKlass.cpp Fri Oct 05 17:17:17 2018 +0200
@@ -1201,9 +1201,6 @@
}
objArrayOop InstanceKlass::allocate_objArray(int n, int length, TRAPS) {
- if (length < 0) {
- THROW_MSG_0(vmSymbols::java_lang_NegativeArraySizeException(), err_msg("%d", length));
- }
check_array_allocation_length(length, arrayOopDesc::max_array_length(T_OBJECT), CHECK_NULL);
int size = objArrayOopDesc::object_size(length);
Klass* ak = array_klass(n, CHECK_NULL);
diff -r 4d36f5998a8b src/hotspot/share/oops/klass.cpp
--- a/src/hotspot/share/oops/klass.cpp Fri Oct 05 17:04:06 2018 +0200
+++ b/src/hotspot/share/oops/klass.cpp Fri Oct 05 17:17:17 2018 +0200
@@ -620,6 +620,8 @@
} else {
THROW_OOP(Universe::out_of_memory_error_retry());
}
+ } else if (length < 0) {
+ THROW_MSG(vmSymbols::java_lang_NegativeArraySizeException(), err_msg("%d", length));
}
}
diff -r 4d36f5998a8b src/hotspot/share/oops/klass.hpp
--- a/src/hotspot/share/oops/klass.hpp Fri Oct 05 17:04:06 2018 +0200
+++ b/src/hotspot/share/oops/klass.hpp Fri Oct 05 17:17:17 2018 +0200
@@ -514,7 +514,7 @@
virtual Klass* array_klass_impl(bool or_null, int rank, TRAPS);
virtual Klass* array_klass_impl(bool or_null, TRAPS);
- // Error handling when length > max_length
+ // Error handling when length > max_length or length < 0
static void check_array_allocation_length(int length, int max_length, TRAPS);
void set_vtable_length(int len) { _vtable_len= len; }
diff -r 4d36f5998a8b src/hotspot/share/oops/objArrayKlass.cpp
--- a/src/hotspot/share/oops/objArrayKlass.cpp Fri Oct 05 17:04:06 2018 +0200
+++ b/src/hotspot/share/oops/objArrayKlass.cpp Fri Oct 05 17:17:17 2018 +0200
@@ -170,14 +170,10 @@
}
objArrayOop ObjArrayKlass::allocate(int length, TRAPS) {
- if (length >= 0) {
- check_array_allocation_length(length, arrayOopDesc::max_array_length(T_OBJECT), CHECK_0);
- int size = objArrayOopDesc::object_size(length);
- return (objArrayOop)Universe::heap()->array_allocate(this, size, length,
- /* do_zero */ true, THREAD);
- } else {
- THROW_MSG_0(vmSymbols::java_lang_NegativeArraySizeException(), err_msg("%d", length));
- }
+ check_array_allocation_length(length, arrayOopDesc::max_array_length(T_OBJECT), CHECK_0);
+ int size = objArrayOopDesc::object_size(length);
+ return (objArrayOop)Universe::heap()->array_allocate(this, size, length,
+ /* do_zero */ true, THREAD);
}
static int multi_alloc_counter = 0;
diff -r 4d36f5998a8b src/hotspot/share/oops/typeArrayKlass.cpp
--- a/src/hotspot/share/oops/typeArrayKlass.cpp Fri Oct 05 17:04:06 2018 +0200
+++ b/src/hotspot/share/oops/typeArrayKlass.cpp Fri Oct 05 17:17:17 2018 +0200
@@ -99,14 +99,10 @@
typeArrayOop TypeArrayKlass::allocate_common(int length, bool do_zero, TRAPS) {
assert(log2_element_size() >= 0, "bad scale");
- if (length >= 0) {
- check_array_allocation_length(length, max_length(), CHECK_NULL);
- size_t size = typeArrayOopDesc::object_size(layout_helper(), length);
- return (typeArrayOop)Universe::heap()->array_allocate(this, (int)size, length,
- do_zero, CHECK_NULL);
- } else {
- THROW_MSG_0(vmSymbols::java_lang_NegativeArraySizeException(), err_msg("%d", length));
- }
+ check_array_allocation_length(length, max_length(), CHECK_NULL);
+ size_t size = typeArrayOopDesc::object_size(layout_helper(), length);
+ return (typeArrayOop)Universe::heap()->array_allocate(this, (int)size, length,
+ do_zero, CHECK_NULL);
}
oop TypeArrayKlass::multi_allocate(int rank, jint* last_size, TRAPS) {
-----------------------------------------------------------------------------------------------
http://cr.openjdk.java.net/~dnsimon/8208686v2
-Doug
Post by Doug Simon
Thanks for the reviews Serguei and Vladimir.
Unless I hear objections in the next 24 hours, I'll push this webrev.
-Doug
Post by s***@oracle.com
Hi Doug,
The JVMTI related part looks good to me.
Thank you for fixing it!
Thanks,
Serguei
Post by Doug Simon
It would be great to get some input from the non-compilers teams on this RFR.
-Doug
To let you know, me and Tom R. did review these changes and agreed that it is the least intrusive changes for Hotspot shared code.
Thanks,
Vladimir
Dan
A major design point of Graal is to treat allocations as non-side effecting to give more freedom to the optimizer by reducing the number of distinct FrameStates that need to be managed. When failing an allocation, Graal will deoptimize to the last side effecting instruction before the allocation. This mean the VM code for heap allocation will potentially be executed twice, once from Graal compiled code and then again in the interpreter. While this is perfectly fine according to the JVM specification, it can cause confusing behavior for JVMTI based tools. They will receive 2 ResourceExhausted events for a single allocation. Furthermore, the first ResourceExhausted event (on the Graal allocation slow path) might denote a bytecode instruction that performs no allocation, making it hard to debug the memory failure.
The proposed solution is to add an extra set of JVMCI VM runtime calls for allocation. These entry points will attempt the allocation and upon failure,
skip side-effects such as posting JVMTI events or handling -XX:OnOutOfMemoryError. The compiled code using these entry points is expected deoptmize on null.
* Returning null instead of throwing an exception on failure.
* Adding a `bool null_on_fail` argument to all relevant methods.
* Adding extra null checking where necessary after each call to these methods when `null_on_fail == true`.
This represents a significant number of changes.
Instead, the proposed solution introduces a new _in_retryable_allocation thread-local. This way, only the entry points and allocation routines that raise an exception need to be modified. Failure is communicated back to the new entry points by throwing a special pre-allocated OOME object (i.e., Universe::out_of_memory_error_retry()) which must not propagate back to Java code. Use of this object is not strictly necessary; it is introduced to highlight/document the special allocation mode.
The proposed solution is at http://cr.openjdk.java.net/~dnsimon/8208686.
THE JBS bug is: https://bugs.openjdk.java.net/browse/JDK-8208686
-Doug
Doug Simon
2018-10-05 17:58:56 UTC
Permalink
Thanks for the review.

-Doug
Post by Vladimir Kozlov
Looks good.
tom
Post by Doug Simon
Hi,
-----------------------------------------------------------------------------------------------
-// Manages a scope in which a failed heap allocation will throw an exception.
-// The pending exception is cleared when leaving the scope.
+// Manages a scope for a JVMCI runtime call that attempts a heap allocation.
+// If there is a pending exception upon closing the scope and the runtime
+// call is of the variety where allocation failure returns NULL without an
+// 1. The pending exception 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 {
JavaThread* _thread;
RetryableAllocationMark(JavaThread* thread, bool activate) {
if (activate) {
- assert(thread->in_retryable_allocation(), "retryable allocation scope is non-reentrant");
+ assert(!thread->in_retryable_allocation(), "retryable allocation scope is non-reentrant");
_thread = thread;
_thread->set_in_retryable_allocation(true);
} else {
@@ -136,6 +141,7 @@
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(NULL);
}
}
}
-----------------------------------------------------------------------------------------------
-----------------------------------------------------------------------------------------------
diff -r 4d36f5998a8b src/hotspot/share/oops/arrayKlass.cpp
--- a/src/hotspot/share/oops/arrayKlass.cpp Fri Oct 05 17:04:06 2018 +0200
+++ b/src/hotspot/share/oops/arrayKlass.cpp Fri Oct 05 17:17:17 2018 +0200
@@ -130,9 +130,6 @@
}
objArrayOop ArrayKlass::allocate_arrayArray(int n, int length, TRAPS) {
- if (length < 0) {
- THROW_MSG_0(vmSymbols::java_lang_NegativeArraySizeException(), err_msg("%d", length));
- }
check_array_allocation_length(length, arrayOopDesc::max_array_length(T_ARRAY), CHECK_0);
int size = objArrayOopDesc::object_size(length);
Klass* k = array_klass(n+dimension(), CHECK_0);
diff -r 4d36f5998a8b src/hotspot/share/oops/instanceKlass.cpp
--- a/src/hotspot/share/oops/instanceKlass.cpp Fri Oct 05 17:04:06 2018 +0200
+++ b/src/hotspot/share/oops/instanceKlass.cpp Fri Oct 05 17:17:17 2018 +0200
@@ -1201,9 +1201,6 @@
}
objArrayOop InstanceKlass::allocate_objArray(int n, int length, TRAPS) {
- if (length < 0) {
- THROW_MSG_0(vmSymbols::java_lang_NegativeArraySizeException(), err_msg("%d", length));
- }
check_array_allocation_length(length, arrayOopDesc::max_array_length(T_OBJECT), CHECK_NULL);
int size = objArrayOopDesc::object_size(length);
Klass* ak = array_klass(n, CHECK_NULL);
diff -r 4d36f5998a8b src/hotspot/share/oops/klass.cpp
--- a/src/hotspot/share/oops/klass.cpp Fri Oct 05 17:04:06 2018 +0200
+++ b/src/hotspot/share/oops/klass.cpp Fri Oct 05 17:17:17 2018 +0200
@@ -620,6 +620,8 @@
} else {
THROW_OOP(Universe::out_of_memory_error_retry());
}
+ } else if (length < 0) {
+ THROW_MSG(vmSymbols::java_lang_NegativeArraySizeException(), err_msg("%d", length));
}
}
diff -r 4d36f5998a8b src/hotspot/share/oops/klass.hpp
--- a/src/hotspot/share/oops/klass.hpp Fri Oct 05 17:04:06 2018 +0200
+++ b/src/hotspot/share/oops/klass.hpp Fri Oct 05 17:17:17 2018 +0200
@@ -514,7 +514,7 @@
virtual Klass* array_klass_impl(bool or_null, int rank, TRAPS);
virtual Klass* array_klass_impl(bool or_null, TRAPS);
- // Error handling when length > max_length
+ // Error handling when length > max_length or length < 0
static void check_array_allocation_length(int length, int max_length, TRAPS);
void set_vtable_length(int len) { _vtable_len= len; }
diff -r 4d36f5998a8b src/hotspot/share/oops/objArrayKlass.cpp
--- a/src/hotspot/share/oops/objArrayKlass.cpp Fri Oct 05 17:04:06 2018 +0200
+++ b/src/hotspot/share/oops/objArrayKlass.cpp Fri Oct 05 17:17:17 2018 +0200
@@ -170,14 +170,10 @@
}
objArrayOop ObjArrayKlass::allocate(int length, TRAPS) {
- if (length >= 0) {
- check_array_allocation_length(length, arrayOopDesc::max_array_length(T_OBJECT), CHECK_0);
- int size = objArrayOopDesc::object_size(length);
- return (objArrayOop)Universe::heap()->array_allocate(this, size, length,
- /* do_zero */ true, THREAD);
- } else {
- THROW_MSG_0(vmSymbols::java_lang_NegativeArraySizeException(), err_msg("%d", length));
- }
+ check_array_allocation_length(length, arrayOopDesc::max_array_length(T_OBJECT), CHECK_0);
+ int size = objArrayOopDesc::object_size(length);
+ return (objArrayOop)Universe::heap()->array_allocate(this, size, length,
+ /* do_zero */ true, THREAD);
}
static int multi_alloc_counter = 0;
diff -r 4d36f5998a8b src/hotspot/share/oops/typeArrayKlass.cpp
--- a/src/hotspot/share/oops/typeArrayKlass.cpp Fri Oct 05 17:04:06 2018 +0200
+++ b/src/hotspot/share/oops/typeArrayKlass.cpp Fri Oct 05 17:17:17 2018 +0200
@@ -99,14 +99,10 @@
typeArrayOop TypeArrayKlass::allocate_common(int length, bool do_zero, TRAPS) {
assert(log2_element_size() >= 0, "bad scale");
- if (length >= 0) {
- check_array_allocation_length(length, max_length(), CHECK_NULL);
- size_t size = typeArrayOopDesc::object_size(layout_helper(), length);
- return (typeArrayOop)Universe::heap()->array_allocate(this, (int)size, length,
- do_zero, CHECK_NULL);
- } else {
- THROW_MSG_0(vmSymbols::java_lang_NegativeArraySizeException(), err_msg("%d", length));
- }
+ check_array_allocation_length(length, max_length(), CHECK_NULL);
+ size_t size = typeArrayOopDesc::object_size(layout_helper(), length);
+ return (typeArrayOop)Universe::heap()->array_allocate(this, (int)size, length,
+ do_zero, CHECK_NULL);
}
oop TypeArrayKlass::multi_allocate(int rank, jint* last_size, TRAPS) {
-----------------------------------------------------------------------------------------------
http://cr.openjdk.java.net/~dnsimon/8208686v2
-Doug
Post by Doug Simon
Thanks for the reviews Serguei and Vladimir.
Unless I hear objections in the next 24 hours, I'll push this webrev.
-Doug
Post by s***@oracle.com
Hi Doug,
The JVMTI related part looks good to me.
Thank you for fixing it!
Thanks,
Serguei
Post by Doug Simon
It would be great to get some input from the non-compilers teams on this RFR.
-Doug
To let you know, me and Tom R. did review these changes and agreed that it is the least intrusive changes for Hotspot shared code.
Thanks,
Vladimir
Dan
A major design point of Graal is to treat allocations as non-side effecting to give more freedom to the optimizer by reducing the number of distinct FrameStates that need to be managed. When failing an allocation, Graal will deoptimize to the last side effecting instruction before the allocation. This mean the VM code for heap allocation will potentially be executed twice, once from Graal compiled code and then again in the interpreter. While this is perfectly fine according to the JVM specification, it can cause confusing behavior for JVMTI based tools. They will receive 2 ResourceExhausted events for a single allocation. Furthermore, the first ResourceExhausted event (on the Graal allocation slow path) might denote a bytecode instruction that performs no allocation, making it hard to debug the memory failure.
The proposed solution is to add an extra set of JVMCI VM runtime calls for allocation. These entry points will attempt the allocation and upon failure,
skip side-effects such as posting JVMTI events or handling -XX:OnOutOfMemoryError. The compiled code using these entry points is expected deoptmize on null.
* Returning null instead of throwing an exception on failure.
* Adding a `bool null_on_fail` argument to all relevant methods.
* Adding extra null checking where necessary after each call to these methods when `null_on_fail == true`.
This represents a significant number of changes.
Instead, the proposed solution introduces a new _in_retryable_allocation thread-local. This way, only the entry points and allocation routines that raise an exception need to be modified. Failure is communicated back to the new entry points by throwing a special pre-allocated OOME object (i.e., Universe::out_of_memory_error_retry()) which must not propagate back to Java code. Use of this object is not strictly necessary; it is introduced to highlight/document the special allocation mode.
The proposed solution is at http://cr.openjdk.java.net/~dnsimon/8208686.
THE JBS bug is: https://bugs.openjdk.java.net/browse/JDK-8208686
-Doug
Loading...