8293989: [JVMCI] re-use cleared oop handles

Reviewed-by: never
This commit is contained in:
Doug Simon 2022-09-20 07:55:22 +00:00
parent 0fa7d9e8cd
commit fe541f05cc
9 changed files with 130 additions and 103 deletions

@ -2208,11 +2208,9 @@ C2V_VMENTRY_0(jint, arrayIndexScale, (JNIEnv* env, jobject, jchar type_char))
return type2aelembytes(type);
C2V_END
C2V_VMENTRY(void, deleteGlobalHandle, (JNIEnv* env, jobject, jlong handle))
if (handle != 0) {
JVMCIENV->runtime()->destroy_oop_handle(handle);
}
}
C2V_VMENTRY(void, releaseClearedOopHandles, (JNIEnv* env, jobject))
JVMCIENV->runtime()->release_cleared_oop_handles();
C2V_END
static void requireJVMCINativeLibrary(JVMCI_TRAPS) {
if (!UseJVMCINativeLibrary) {
@ -2903,7 +2901,7 @@ JNINativeMethod CompilerToVM::methods[] = {
{CC "readArrayElement", CC "(" OBJECTCONSTANT "I)Ljava/lang/Object;", FN_PTR(readArrayElement)},
{CC "arrayBaseOffset", CC "(C)I", FN_PTR(arrayBaseOffset)},
{CC "arrayIndexScale", CC "(C)I", FN_PTR(arrayIndexScale)},
{CC "deleteGlobalHandle", CC "(J)V", FN_PTR(deleteGlobalHandle)},
{CC "releaseClearedOopHandles", CC "()V", FN_PTR(releaseClearedOopHandles)},
{CC "registerNativeMethods", CC "(" CLASS ")[J", FN_PTR(registerNativeMethods)},
{CC "isCurrentThreadAttached", CC "()Z", FN_PTR(isCurrentThreadAttached)},
{CC "getCurrentJavaThread", CC "()J", FN_PTR(getCurrentJavaThread)},

@ -843,94 +843,107 @@ static OopStorage* object_handles() {
jlong JVMCIRuntime::make_oop_handle(const Handle& obj) {
assert(!Universe::heap()->is_gc_active(), "can't extend the root set during GC");
assert(oopDesc::is_oop(obj()), "not an oop");
oop* ptr = object_handles()->allocate();
jlong res = 0;
if (ptr != nullptr) {
assert(*ptr == nullptr, "invariant");
NativeAccess<>::oop_store(ptr, obj());
res = (jlong) ptr;
} else {
vm_exit_out_of_memory(sizeof(oop), OOM_MALLOC_ERROR,
"Cannot create JVMCI oop handle");
}
oop* ptr = OopHandle(object_handles(), obj()).ptr_raw();
MutexLocker ml(_lock);
_oop_handles.append(ptr);
return res;
return (jlong) ptr;
}
bool JVMCIRuntime::probe_oop_handle(jlong handle, int index) {
oop* key = (oop*) handle;
if (key == _oop_handles.at(index)) {
_last_found_oop_handle_index = index;
return true;
}
return false;
}
int JVMCIRuntime::find_oop_handle(jlong handle) {
int len = _oop_handles.length();
int next = _last_found_oop_handle_index + 1;
int prev = MAX2(_last_found_oop_handle_index, 0) - 1;
// Search "outwards" from the index of the last found
// entry. Experimentation shows that this significantly
// reduces the amount of searching performed.
do {
if (next < len) {
if (probe_oop_handle(handle, next)) {
return next;
}
next++;
}
if (prev >= 0) {
if (probe_oop_handle(handle, prev)) {
return prev;
}
prev--;
}
} while (next - (prev + 1) < len);
return -1;
}
int JVMCIRuntime::release_and_clear_globals() {
int released = 0;
int JVMCIRuntime::release_and_clear_oop_handles() {
guarantee(_num_attached_threads == cannot_be_attached, "only call during JVMCI runtime shutdown");
int released = release_cleared_oop_handles();
if (_oop_handles.length() != 0) {
// Squash non-null JNI handles to front of _oop_handles for
// the bulk release operation
for (int i = 0; i < _oop_handles.length(); i++) {
oop* oop_ptr = _oop_handles.at(i);
if (oop_ptr != nullptr) {
// Satisfy OopHandles::release precondition that all
// handles being released are null.
NativeAccess<>::oop_store(oop_ptr, (oop) NULL);
_oop_handles.at_put(released++, oop_ptr);
}
guarantee(oop_ptr != nullptr, "release_cleared_oop_handles left null entry in _oop_handles");
guarantee(*oop_ptr != nullptr, "unexpected cleared handle");
// Satisfy OopHandles::release precondition that all
// handles being released are null.
NativeAccess<>::oop_store(oop_ptr, (oop) NULL);
}
// Do the bulk release
object_handles()->release(_oop_handles.adr_at(0), released);
object_handles()->release(_oop_handles.adr_at(0), _oop_handles.length());
released += _oop_handles.length();
}
_oop_handles.clear();
_last_found_oop_handle_index = -1;
return released;
}
void JVMCIRuntime::destroy_oop_handle(jlong handle) {
// Assert before nulling out, for better debugging.
assert(is_oop_handle(handle), "precondition");
oop* oop_ptr = (oop*) handle;
NativeAccess<>::oop_store(oop_ptr, (oop) nullptr);
object_handles()->release(oop_ptr);
MutexLocker ml(_lock);
int index = find_oop_handle(handle);
guarantee(index != -1, "global not allocated in JVMCI runtime %d: " INTPTR_FORMAT, id(), handle);
_oop_handles.at_put(index, nullptr);
static bool is_referent_non_null(oop* handle) {
return handle != nullptr && *handle != nullptr;
}
bool JVMCIRuntime::is_oop_handle(jlong handle) {
const oop* ptr = (oop*) handle;
return object_handles()->allocation_status(ptr) == OopStorage::ALLOCATED_ENTRY;
// Swaps the elements in `array` at index `a` and index `b`
static void swap(GrowableArray<oop*>* array, int a, int b) {
oop* tmp = array->at(a);
array->at_put(a, array->at(b));
array->at_put(b, tmp);
}
int JVMCIRuntime::release_cleared_oop_handles() {
// Despite this lock, it's possible for another thread
// to clear a handle's referent concurrently (e.g., a thread
// executing IndirectHotSpotObjectConstantImpl.clear()).
// This is benign - it means there can still be cleared
// handles in _oop_handles when this method returns.
MutexLocker ml(_lock);
int next = 0;
if (_oop_handles.length() != 0) {
// Key for _oop_handles contents in example below:
// H: handle with non-null referent
// h: handle with clear (i.e., null) referent
// -: null entry
// Shuffle all handles with non-null referents to the front of the list
// Example: Before: 0HHh-Hh-
// After: HHHh--h-
for (int i = 0; i < _oop_handles.length(); i++) {
oop* handle = _oop_handles.at(i);
if (is_referent_non_null(handle)) {
if (i != next && !is_referent_non_null(_oop_handles.at(next))) {
// Swap elements at index `next` and `i`
swap(&_oop_handles, next, i);
}
next++;
}
}
// `next` is now the index of the first null handle or handle with a null referent
int num_alive = next;
// Shuffle all null handles to the end of the list
// Example: Before: HHHh--h-
// After: HHHhh---
// num_alive: 3
for (int i = next; i < _oop_handles.length(); i++) {
oop* handle = _oop_handles.at(i);
if (handle != nullptr) {
if (i != next && _oop_handles.at(next) == nullptr) {
// Swap elements at index `next` and `i`
swap(&_oop_handles, next, i);
}
next++;
}
}
int to_release = next - num_alive;
// `next` is now the index of the first null handle
// Example: to_release: 2
// Bulk release the handles with a null referent
object_handles()->release(_oop_handles.adr_at(num_alive), to_release);
// Truncate oop handles to only those with a non-null referent
JVMCI_event_1("compacted oop handles in JVMCI runtime %d from %d to %d", _id, _oop_handles.length(), num_alive);
_oop_handles.trunc_to(num_alive);
// Example: HHH
return to_release;
}
return 0;
}
jmetadata JVMCIRuntime::allocate_handle(const methodHandle& handle) {
@ -988,8 +1001,7 @@ JVMCIRuntime::JVMCIRuntime(JVMCIRuntime* next, int id, bool for_compile_broker)
_metadata_handles(new MetadataHandles()),
_oop_handles(100, mtJVMCI),
_num_attached_threads(0),
_for_compile_broker(for_compile_broker),
_last_found_oop_handle_index(-1)
_for_compile_broker(for_compile_broker)
{
if (id == -1) {
_lock = JVMCIRuntime_lock;
@ -1169,7 +1181,7 @@ bool JVMCIRuntime::detach_thread(JavaThread* thread, const char* reason, bool ca
// that could be using them. Handles for the Java JVMCI runtime
// are never released as we cannot guarantee all compiler threads
// using it have been stopped.
int released = release_and_clear_globals();
int released = release_and_clear_oop_handles();
JVMCI_event_1("releasing handles for JVMCI runtime %d: oop handles=%d, metadata handles={total=%d, live=%d, blocks=%d}",
_id,
released,

@ -223,14 +223,9 @@ class JVMCIRuntime: public CHeapObj<mtJVMCI> {
// JVMCI_lock must be held by current thread
static JVMCIRuntime* select_runtime_in_shutdown(JavaThread* thread);
// Helpers for destroy_oop_handle
int _last_found_oop_handle_index;
bool probe_oop_handle(jlong handle, int index);
int find_oop_handle(jlong handle);
// Releases all the non-null entries in _oop_handles and then clears
// the list. Returns the number of non-null entries prior to clearing.
int release_and_clear_globals();
// the list. Returns the number released handles.
int release_and_clear_oop_handles();
public:
JVMCIRuntime(JVMCIRuntime* next, int id, bool for_compile_broker);
@ -277,10 +272,12 @@ class JVMCIRuntime: public CHeapObj<mtJVMCI> {
// used when creating an IndirectHotSpotObjectConstantImpl in the
// shared library JavaVM.
jlong make_oop_handle(const Handle& obj);
bool is_oop_handle(jlong handle);
// Called from IndirectHotSpotObjectConstantImpl.clear(Object)
void destroy_oop_handle(jlong handle);
// Releases all the non-null entries in _oop_handles whose referent is null.
// Returns the number of handles released by this call.
// The method also resets _last_found_oop_handle_index to -1
// and _null_oop_handles to 0.
int release_cleared_oop_handles();
// Allocation and management of metadata handles.
jmetadata allocate_handle(const methodHandle& handle);

@ -97,19 +97,29 @@ abstract class Cleaner extends WeakReference<Object> {
/**
* Performs the cleanup action now that this object's referent has become weakly reachable.
*
* @returns true if the clean up action cleared the referent of an oop handle and requires a
* subsequent call to {@link CompilerToVM#releaseClearedOopHandles()} to reclaim the
* resources of the handle itself
*/
abstract void doCleanup();
abstract boolean doCleanup();
/**
* Remove the cleaners whose referents have become weakly reachable.
*/
static void clean() {
Cleaner c = (Cleaner) queue.poll();
boolean oopHandleCleared = false;
while (c != null) {
remove(c);
c.doCleanup();
if (c.doCleanup()) {
oopHandleCleared = true;
}
c = (Cleaner) queue.poll();
}
if (oopHandleCleared) {
CompilerToVM.compilerToVM().releaseClearedOopHandles();
}
}
/**

@ -1175,10 +1175,9 @@ final class CompilerToVM {
native boolean isTrustedForIntrinsics(HotSpotResolvedObjectTypeImpl klass, long klassPointer);
/**
* Releases the resources backing the global JNI {@code handle}. This is equivalent to the
* {@code DeleteGlobalRef} JNI function.
* Releases all oop handles whose referent is null.
*/
native void deleteGlobalHandle(long handle);
native void releaseClearedOopHandles();
/**
* Gets the failed speculations pointed to by {@code *failedSpeculationsAddress}.

@ -57,18 +57,17 @@ final class HandleCleaner extends Cleaner {
* Releases the resource associated with {@code this.handle}.
*/
@Override
void doCleanup() {
boolean doCleanup() {
if (isJObject) {
// The sentinel value used to denote a free handle is
// an object on the HotSpot heap so we call into the
// VM to set the target of an object handle to this value.
CompilerToVM.compilerToVM().deleteGlobalHandle(handle);
IndirectHotSpotObjectConstantImpl.clearHandle(handle);
return true;
} else {
// Setting the target of a jmetadata handle to 0 enables
// the handle to be reused. See MetadataHandles in
// metadataHandles.hpp for more info.
long value = UNSAFE.getLong(null, handle);
UNSAFE.compareAndSetLong(null, handle, value, 0);
return false;
}
}

@ -115,6 +115,7 @@ public final class HotSpotObjectConstantScope implements AutoCloseable {
obj.clear(localScopeDescription);
}
foreignObjects = null;
CompilerToVM.compilerToVM().releaseClearedOopHandles();
}
CURRENT.set(parent);
}

@ -354,12 +354,13 @@ public class HotSpotSpeculationLog implements SpeculationLog {
}
@Override
void doCleanup() {
boolean doCleanup() {
long pointer = UnsafeAccess.UNSAFE.getAddress(address);
if (pointer != 0) {
compilerToVM().releaseFailedSpeculations(address);
}
UnsafeAccess.UNSAFE.freeMemory(address);
return false;
}
final long address;

@ -23,6 +23,7 @@
package jdk.vm.ci.hotspot;
import static jdk.vm.ci.hotspot.HotSpotJVMCIRuntime.runtime;
import static jdk.vm.ci.hotspot.UnsafeAccess.UNSAFE;
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
@ -144,13 +145,22 @@ final class IndirectHotSpotObjectConstantImpl extends HotSpotObjectConstantImpl
*/
void clear(Object scopeDescription) {
checkHandle();
CompilerToVM.compilerToVM().deleteGlobalHandle(objectHandle);
if (rawAudit == null) {
rawAudit = scopeDescription;
}
clearHandle(objectHandle);
objectHandle = 0L;
}
/**
* Sets the referent of {@code handle} to 0 so that it will be reclaimed when calling
* {@link CompilerToVM#releaseClearedOopHandles}.
*/
static void clearHandle(long handle) {
UNSAFE.putLong(handle, 0);
}
@Override
public JavaConstant compress() {
assert !compressed;