Skip to content

Commit fe541f0

Browse files
author
Doug Simon
committed
8293989: [JVMCI] re-use cleared oop handles
Reviewed-by: never
1 parent 0fa7d9e commit fe541f0

File tree

9 files changed

+128
-101
lines changed

9 files changed

+128
-101
lines changed

src/hotspot/share/jvmci/jvmciCompilerToVM.cpp

+4-6
Original file line numberDiff line numberDiff line change
@@ -2208,11 +2208,9 @@ C2V_VMENTRY_0(jint, arrayIndexScale, (JNIEnv* env, jobject, jchar type_char))
22082208
return type2aelembytes(type);
22092209
C2V_END
22102210

2211-
C2V_VMENTRY(void, deleteGlobalHandle, (JNIEnv* env, jobject, jlong handle))
2212-
if (handle != 0) {
2213-
JVMCIENV->runtime()->destroy_oop_handle(handle);
2214-
}
2215-
}
2211+
C2V_VMENTRY(void, releaseClearedOopHandles, (JNIEnv* env, jobject))
2212+
JVMCIENV->runtime()->release_cleared_oop_handles();
2213+
C2V_END
22162214

22172215
static void requireJVMCINativeLibrary(JVMCI_TRAPS) {
22182216
if (!UseJVMCINativeLibrary) {
@@ -2903,7 +2901,7 @@ JNINativeMethod CompilerToVM::methods[] = {
29032901
{CC "readArrayElement", CC "(" OBJECTCONSTANT "I)Ljava/lang/Object;", FN_PTR(readArrayElement)},
29042902
{CC "arrayBaseOffset", CC "(C)I", FN_PTR(arrayBaseOffset)},
29052903
{CC "arrayIndexScale", CC "(C)I", FN_PTR(arrayIndexScale)},
2906-
{CC "deleteGlobalHandle", CC "(J)V", FN_PTR(deleteGlobalHandle)},
2904+
{CC "releaseClearedOopHandles", CC "()V", FN_PTR(releaseClearedOopHandles)},
29072905
{CC "registerNativeMethods", CC "(" CLASS ")[J", FN_PTR(registerNativeMethods)},
29082906
{CC "isCurrentThreadAttached", CC "()Z", FN_PTR(isCurrentThreadAttached)},
29092907
{CC "getCurrentJavaThread", CC "()J", FN_PTR(getCurrentJavaThread)},

src/hotspot/share/jvmci/jvmciRuntime.cpp

+85-73
Original file line numberDiff line numberDiff line change
@@ -843,94 +843,107 @@ static OopStorage* object_handles() {
843843
jlong JVMCIRuntime::make_oop_handle(const Handle& obj) {
844844
assert(!Universe::heap()->is_gc_active(), "can't extend the root set during GC");
845845
assert(oopDesc::is_oop(obj()), "not an oop");
846-
oop* ptr = object_handles()->allocate();
847-
jlong res = 0;
848-
if (ptr != nullptr) {
849-
assert(*ptr == nullptr, "invariant");
850-
NativeAccess<>::oop_store(ptr, obj());
851-
res = (jlong) ptr;
852-
} else {
853-
vm_exit_out_of_memory(sizeof(oop), OOM_MALLOC_ERROR,
854-
"Cannot create JVMCI oop handle");
855-
}
846+
847+
oop* ptr = OopHandle(object_handles(), obj()).ptr_raw();
856848
MutexLocker ml(_lock);
857849
_oop_handles.append(ptr);
858-
return res;
850+
return (jlong) ptr;
859851
}
860852

861-
bool JVMCIRuntime::probe_oop_handle(jlong handle, int index) {
862-
oop* key = (oop*) handle;
863-
if (key == _oop_handles.at(index)) {
864-
_last_found_oop_handle_index = index;
865-
return true;
853+
int JVMCIRuntime::release_and_clear_oop_handles() {
854+
guarantee(_num_attached_threads == cannot_be_attached, "only call during JVMCI runtime shutdown");
855+
int released = release_cleared_oop_handles();
856+
if (_oop_handles.length() != 0) {
857+
for (int i = 0; i < _oop_handles.length(); i++) {
858+
oop* oop_ptr = _oop_handles.at(i);
859+
guarantee(oop_ptr != nullptr, "release_cleared_oop_handles left null entry in _oop_handles");
860+
guarantee(*oop_ptr != nullptr, "unexpected cleared handle");
861+
// Satisfy OopHandles::release precondition that all
862+
// handles being released are null.
863+
NativeAccess<>::oop_store(oop_ptr, (oop) NULL);
864+
}
865+
866+
// Do the bulk release
867+
object_handles()->release(_oop_handles.adr_at(0), _oop_handles.length());
868+
released += _oop_handles.length();
866869
}
867-
return false;
870+
_oop_handles.clear();
871+
return released;
868872
}
869873

870-
int JVMCIRuntime::find_oop_handle(jlong handle) {
871-
int len = _oop_handles.length();
872-
int next = _last_found_oop_handle_index + 1;
873-
int prev = MAX2(_last_found_oop_handle_index, 0) - 1;
874+
static bool is_referent_non_null(oop* handle) {
875+
return handle != nullptr && *handle != nullptr;
876+
}
874877

875-
// Search "outwards" from the index of the last found
876-
// entry. Experimentation shows that this significantly
877-
// reduces the amount of searching performed.
878-
do {
879-
if (next < len) {
880-
if (probe_oop_handle(handle, next)) {
881-
return next;
882-
}
883-
next++;
884-
}
885-
if (prev >= 0) {
886-
if (probe_oop_handle(handle, prev)) {
887-
return prev;
888-
}
889-
prev--;
890-
}
891-
} while (next - (prev + 1) < len);
892-
return -1;
878+
// Swaps the elements in `array` at index `a` and index `b`
879+
static void swap(GrowableArray<oop*>* array, int a, int b) {
880+
oop* tmp = array->at(a);
881+
array->at_put(a, array->at(b));
882+
array->at_put(b, tmp);
893883
}
894884

895-
int JVMCIRuntime::release_and_clear_globals() {
896-
int released = 0;
885+
int JVMCIRuntime::release_cleared_oop_handles() {
886+
// Despite this lock, it's possible for another thread
887+
// to clear a handle's referent concurrently (e.g., a thread
888+
// executing IndirectHotSpotObjectConstantImpl.clear()).
889+
// This is benign - it means there can still be cleared
890+
// handles in _oop_handles when this method returns.
891+
MutexLocker ml(_lock);
892+
893+
int next = 0;
897894
if (_oop_handles.length() != 0) {
898-
// Squash non-null JNI handles to front of _oop_handles for
899-
// the bulk release operation
895+
// Key for _oop_handles contents in example below:
896+
// H: handle with non-null referent
897+
// h: handle with clear (i.e., null) referent
898+
// -: null entry
899+
900+
// Shuffle all handles with non-null referents to the front of the list
901+
// Example: Before: 0HHh-Hh-
902+
// After: HHHh--h-
900903
for (int i = 0; i < _oop_handles.length(); i++) {
901-
oop* oop_ptr = _oop_handles.at(i);
902-
if (oop_ptr != nullptr) {
903-
// Satisfy OopHandles::release precondition that all
904-
// handles being released are null.
905-
NativeAccess<>::oop_store(oop_ptr, (oop) NULL);
904+
oop* handle = _oop_handles.at(i);
905+
if (is_referent_non_null(handle)) {
906+
if (i != next && !is_referent_non_null(_oop_handles.at(next))) {
907+
// Swap elements at index `next` and `i`
908+
swap(&_oop_handles, next, i);
909+
}
910+
next++;
911+
}
912+
}
906913

907-
_oop_handles.at_put(released++, oop_ptr);
914+
// `next` is now the index of the first null handle or handle with a null referent
915+
int num_alive = next;
916+
917+
// Shuffle all null handles to the end of the list
918+
// Example: Before: HHHh--h-
919+
// After: HHHhh---
920+
// num_alive: 3
921+
for (int i = next; i < _oop_handles.length(); i++) {
922+
oop* handle = _oop_handles.at(i);
923+
if (handle != nullptr) {
924+
if (i != next && _oop_handles.at(next) == nullptr) {
925+
// Swap elements at index `next` and `i`
926+
swap(&_oop_handles, next, i);
927+
}
928+
next++;
908929
}
909930
}
910-
// Do the bulk release
911-
object_handles()->release(_oop_handles.adr_at(0), released);
912-
}
913-
_oop_handles.clear();
914-
_last_found_oop_handle_index = -1;
915-
return released;
916-
}
931+
int to_release = next - num_alive;
917932

918-
void JVMCIRuntime::destroy_oop_handle(jlong handle) {
919-
// Assert before nulling out, for better debugging.
920-
assert(is_oop_handle(handle), "precondition");
921-
oop* oop_ptr = (oop*) handle;
922-
NativeAccess<>::oop_store(oop_ptr, (oop) nullptr);
923-
object_handles()->release(oop_ptr);
933+
// `next` is now the index of the first null handle
934+
// Example: to_release: 2
924935

925-
MutexLocker ml(_lock);
926-
int index = find_oop_handle(handle);
927-
guarantee(index != -1, "global not allocated in JVMCI runtime %d: " INTPTR_FORMAT, id(), handle);
928-
_oop_handles.at_put(index, nullptr);
929-
}
936+
// Bulk release the handles with a null referent
937+
object_handles()->release(_oop_handles.adr_at(num_alive), to_release);
930938

931-
bool JVMCIRuntime::is_oop_handle(jlong handle) {
932-
const oop* ptr = (oop*) handle;
933-
return object_handles()->allocation_status(ptr) == OopStorage::ALLOCATED_ENTRY;
939+
// Truncate oop handles to only those with a non-null referent
940+
JVMCI_event_1("compacted oop handles in JVMCI runtime %d from %d to %d", _id, _oop_handles.length(), num_alive);
941+
_oop_handles.trunc_to(num_alive);
942+
// Example: HHH
943+
944+
return to_release;
945+
}
946+
return 0;
934947
}
935948

936949
jmetadata JVMCIRuntime::allocate_handle(const methodHandle& handle) {
@@ -988,8 +1001,7 @@ JVMCIRuntime::JVMCIRuntime(JVMCIRuntime* next, int id, bool for_compile_broker)
9881001
_metadata_handles(new MetadataHandles()),
9891002
_oop_handles(100, mtJVMCI),
9901003
_num_attached_threads(0),
991-
_for_compile_broker(for_compile_broker),
992-
_last_found_oop_handle_index(-1)
1004+
_for_compile_broker(for_compile_broker)
9931005
{
9941006
if (id == -1) {
9951007
_lock = JVMCIRuntime_lock;
@@ -1169,7 +1181,7 @@ bool JVMCIRuntime::detach_thread(JavaThread* thread, const char* reason, bool ca
11691181
// that could be using them. Handles for the Java JVMCI runtime
11701182
// are never released as we cannot guarantee all compiler threads
11711183
// using it have been stopped.
1172-
int released = release_and_clear_globals();
1184+
int released = release_and_clear_oop_handles();
11731185
JVMCI_event_1("releasing handles for JVMCI runtime %d: oop handles=%d, metadata handles={total=%d, live=%d, blocks=%d}",
11741186
_id,
11751187
released,

src/hotspot/share/jvmci/jvmciRuntime.hpp

+7-10
Original file line numberDiff line numberDiff line change
@@ -223,14 +223,9 @@ class JVMCIRuntime: public CHeapObj<mtJVMCI> {
223223
// JVMCI_lock must be held by current thread
224224
static JVMCIRuntime* select_runtime_in_shutdown(JavaThread* thread);
225225

226-
// Helpers for destroy_oop_handle
227-
int _last_found_oop_handle_index;
228-
bool probe_oop_handle(jlong handle, int index);
229-
int find_oop_handle(jlong handle);
230-
231226
// Releases all the non-null entries in _oop_handles and then clears
232-
// the list. Returns the number of non-null entries prior to clearing.
233-
int release_and_clear_globals();
227+
// the list. Returns the number released handles.
228+
int release_and_clear_oop_handles();
234229

235230
public:
236231
JVMCIRuntime(JVMCIRuntime* next, int id, bool for_compile_broker);
@@ -277,10 +272,12 @@ class JVMCIRuntime: public CHeapObj<mtJVMCI> {
277272
// used when creating an IndirectHotSpotObjectConstantImpl in the
278273
// shared library JavaVM.
279274
jlong make_oop_handle(const Handle& obj);
280-
bool is_oop_handle(jlong handle);
281275

282-
// Called from IndirectHotSpotObjectConstantImpl.clear(Object)
283-
void destroy_oop_handle(jlong handle);
276+
// Releases all the non-null entries in _oop_handles whose referent is null.
277+
// Returns the number of handles released by this call.
278+
// The method also resets _last_found_oop_handle_index to -1
279+
// and _null_oop_handles to 0.
280+
int release_cleared_oop_handles();
284281

285282
// Allocation and management of metadata handles.
286283
jmetadata allocate_handle(const methodHandle& handle);

src/jdk.internal.vm.ci/share/classes/jdk.vm.ci.hotspot/src/jdk/vm/ci/hotspot/Cleaner.java

+12-2
Original file line numberDiff line numberDiff line change
@@ -97,19 +97,29 @@ private static synchronized void remove(Cleaner cl) {
9797

9898
/**
9999
* Performs the cleanup action now that this object's referent has become weakly reachable.
100+
*
101+
* @returns true if the clean up action cleared the referent of an oop handle and requires a
102+
* subsequent call to {@link CompilerToVM#releaseClearedOopHandles()} to reclaim the
103+
* resources of the handle itself
100104
*/
101-
abstract void doCleanup();
105+
abstract boolean doCleanup();
102106

103107
/**
104108
* Remove the cleaners whose referents have become weakly reachable.
105109
*/
106110
static void clean() {
107111
Cleaner c = (Cleaner) queue.poll();
112+
boolean oopHandleCleared = false;
108113
while (c != null) {
109114
remove(c);
110-
c.doCleanup();
115+
if (c.doCleanup()) {
116+
oopHandleCleared = true;
117+
}
111118
c = (Cleaner) queue.poll();
112119
}
120+
if (oopHandleCleared) {
121+
CompilerToVM.compilerToVM().releaseClearedOopHandles();
122+
}
113123
}
114124

115125
/**

src/jdk.internal.vm.ci/share/classes/jdk.vm.ci.hotspot/src/jdk/vm/ci/hotspot/CompilerToVM.java

+2-3
Original file line numberDiff line numberDiff line change
@@ -1175,10 +1175,9 @@ boolean isTrustedForIntrinsics(HotSpotResolvedObjectTypeImpl klass) {
11751175
native boolean isTrustedForIntrinsics(HotSpotResolvedObjectTypeImpl klass, long klassPointer);
11761176

11771177
/**
1178-
* Releases the resources backing the global JNI {@code handle}. This is equivalent to the
1179-
* {@code DeleteGlobalRef} JNI function.
1178+
* Releases all oop handles whose referent is null.
11801179
*/
1181-
native void deleteGlobalHandle(long handle);
1180+
native void releaseClearedOopHandles();
11821181

11831182
/**
11841183
* Gets the failed speculations pointed to by {@code *failedSpeculationsAddress}.

src/jdk.internal.vm.ci/share/classes/jdk.vm.ci.hotspot/src/jdk/vm/ci/hotspot/HandleCleaner.java

+4-5
Original file line numberDiff line numberDiff line change
@@ -57,18 +57,17 @@ private HandleCleaner(Object wrapper, long handle, boolean isJObject) {
5757
* Releases the resource associated with {@code this.handle}.
5858
*/
5959
@Override
60-
void doCleanup() {
60+
boolean doCleanup() {
6161
if (isJObject) {
62-
// The sentinel value used to denote a free handle is
63-
// an object on the HotSpot heap so we call into the
64-
// VM to set the target of an object handle to this value.
65-
CompilerToVM.compilerToVM().deleteGlobalHandle(handle);
62+
IndirectHotSpotObjectConstantImpl.clearHandle(handle);
63+
return true;
6664
} else {
6765
// Setting the target of a jmetadata handle to 0 enables
6866
// the handle to be reused. See MetadataHandles in
6967
// metadataHandles.hpp for more info.
7068
long value = UNSAFE.getLong(null, handle);
7169
UNSAFE.compareAndSetLong(null, handle, value, 0);
70+
return false;
7271
}
7372
}
7473

src/jdk.internal.vm.ci/share/classes/jdk.vm.ci.hotspot/src/jdk/vm/ci/hotspot/HotSpotObjectConstantScope.java

+1
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ public void close() {
115115
obj.clear(localScopeDescription);
116116
}
117117
foreignObjects = null;
118+
CompilerToVM.compilerToVM().releaseClearedOopHandles();
118119
}
119120
CURRENT.set(parent);
120121
}

src/jdk.internal.vm.ci/share/classes/jdk.vm.ci.hotspot/src/jdk/vm/ci/hotspot/HotSpotSpeculationLog.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -354,12 +354,13 @@ private static final class LogCleaner extends Cleaner {
354354
}
355355

356356
@Override
357-
void doCleanup() {
357+
boolean doCleanup() {
358358
long pointer = UnsafeAccess.UNSAFE.getAddress(address);
359359
if (pointer != 0) {
360360
compilerToVM().releaseFailedSpeculations(address);
361361
}
362362
UnsafeAccess.UNSAFE.freeMemory(address);
363+
return false;
363364
}
364365

365366
final long address;

src/jdk.internal.vm.ci/share/classes/jdk.vm.ci.hotspot/src/jdk/vm/ci/hotspot/IndirectHotSpotObjectConstantImpl.java

+11-1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
package jdk.vm.ci.hotspot;
2424

2525
import static jdk.vm.ci.hotspot.HotSpotJVMCIRuntime.runtime;
26+
import static jdk.vm.ci.hotspot.UnsafeAccess.UNSAFE;
2627

2728
import java.io.ByteArrayOutputStream;
2829
import java.io.PrintStream;
@@ -144,13 +145,22 @@ public HotSpotResolvedObjectType getType() {
144145
*/
145146
void clear(Object scopeDescription) {
146147
checkHandle();
147-
CompilerToVM.compilerToVM().deleteGlobalHandle(objectHandle);
148148
if (rawAudit == null) {
149149
rawAudit = scopeDescription;
150150
}
151+
152+
clearHandle(objectHandle);
151153
objectHandle = 0L;
152154
}
153155

156+
/**
157+
* Sets the referent of {@code handle} to 0 so that it will be reclaimed when calling
158+
* {@link CompilerToVM#releaseClearedOopHandles}.
159+
*/
160+
static void clearHandle(long handle) {
161+
UNSAFE.putLong(handle, 0);
162+
}
163+
154164
@Override
155165
public JavaConstant compress() {
156166
assert !compressed;

0 commit comments

Comments
 (0)