Skip to content
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 76 additions & 5 deletions ddprof-lib/src/main/cpp/hotspot/hotspotSupport.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1335,22 +1335,93 @@ int HotspotSupport::walkJavaStack(StackWalkRequest& request) {
return java_frames;
}

class LockState {
private:
VMClassLoaderData* volatile _cld;
public:
LockState() : _cld(nullptr) {}
~LockState() { reset(); }
void lock(VMClassLoaderData* cld);
void reset();
};

void LockState::lock(VMClassLoaderData* cld) {
cld->lock();
_cld = cld;
}

void LockState::reset() {
// Assume: if _cld->lock() did not fail, _cld->unlock() should not
// fail as well.
// The siglongjmp cannot protect _cld->lock()/unlock() calls
if (_cld != nullptr) {
_cld->unlock();
_cld = nullptr;
}
}

static void patchClassLoaderData(JNIEnv* jni, jclass klass) {
bool needs_patch = VM::hotspot_version() == 8;
if (needs_patch) {
// Workaround for JVM bug https://bugs.openjdk.org/browse/JDK-8062116
// Preallocate space for jmethodIDs at the beginning of the list (rather than at the end)
// This is relevant only for JDK 8 - later versions do not have this bug
if (VMStructs::hasClassLoaderData()) {
ProfiledThread* prof_thread = ProfiledThread::initCurrentThreadSignalSafe();
assert(prof_thread != nullptr);
JmpCtxScope jmp_scope(prof_thread);
sigjmp_buf crash_protection_ctx;
LockState state;
if (sigsetjmp(crash_protection_ctx, 1) != 0) {
jmp_scope.restore();
state.reset();
return;
}
jmp_scope.install(&crash_protection_ctx);
VMKlass *vmklass = VMKlass::fromJavaClass(jni, klass);
int method_count = vmklass->methodCount();
if (method_count > 0) {
VMClassLoaderData *cld = vmklass->classLoaderData();
cld->lock();
for (int i = 0; i < method_count; i += MethodList::SIZE) {
*cld->methodList() = new MethodList(*cld->methodList());
// patchClassLoaderData() re-runs for the same class on every ClassPrepare
// replay (profiler restart via loadAllMethodIDsIfNeeded()), RedefineClasses
// and RetransformClasses -- none of which change method_count in practice.
// Without this tag, each re-run would prepend another full set of
// MethodList blocks onto the classloader-wide list that nothing ever frees.
// The tag lives on the jclass itself, so it disappears with the class --
// no separate bookkeeping to leak or to clean up on unload.
jvmtiEnv* jvmti = VM::jvmti();
jlong already_patched = 0;
if (jvmti == nullptr || jvmti->GetTag(klass, &already_patched) != JVMTI_ERROR_NONE) {
already_patched = 0;
}
if (method_count > already_patched) {
VMClassLoaderData *cld = vmklass->classLoaderData();
if (cld == nullptr) {
return;
}
state.lock(cld);
int i;
for (i = (int) already_patched; i < method_count; i += MethodList::SIZE) {
*cld->methodList() = new MethodList(*cld->methodList());
}
// Release cld's lock before touching the JVMTI tag: cld->lock()
// resolves to HotSpot's Monitor::lock_without_safepoint_check(), and
// GetTag()/SetTag() are full JVMTI entry points that do participate
// in safepoint polling -- calling them while holding a
// safepoint-check-suppressing lock is a JVM-deadlock hazard on its
// own, independent of any fault/siglongjmp path.
state.reset();
// This SetTag() is not serialized against a concurrent
// patchClassLoaderData() call for the same class (e.g.
// RetransformClasses on one thread racing the <clinit> fallback on
// the JFR dump thread): both can read the same stale tag and both
// patch. That only wastes one extra round of preallocated blocks in
// the rare concurrent case -- unlike the unguarded original, it
// cannot grow unboundedly, since the racing calls all still need a
// fresh method_count increase to trigger another round.
if (jvmti != nullptr) {
jvmti->SetTag(klass, i);
}
}
cld->unlock();
}
}
}
Expand Down
Loading