8358429: JFR: minimize the time the Threads_lock is held for sampling
Reviewed-by: egahlin
This commit is contained in:
parent
955bfcd550
commit
b6d60280e7
74
src/hotspot/share/jfr/periodic/sampling/jfrSampleMonitor.hpp
Normal file
74
src/hotspot/share/jfr/periodic/sampling/jfrSampleMonitor.hpp
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
|
||||||
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
|
*
|
||||||
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU General Public License version 2 only, as
|
||||||
|
* published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||||
|
* version 2 for more details (a copy is included in the LICENSE file that
|
||||||
|
* accompanied this code).
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License version
|
||||||
|
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||||
|
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*
|
||||||
|
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||||
|
* or visit www.oracle.com if you need additional information or have any
|
||||||
|
* questions.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef SHARE_JFR_PERIODIC_SAMPLING_JFRSAMPLEMONITOR_HPP
|
||||||
|
#define SHARE_JFR_PERIODIC_SAMPLING_JFRSAMPLEMONITOR_HPP
|
||||||
|
|
||||||
|
#include "jfr/periodic/sampling/jfrSampleRequest.hpp"
|
||||||
|
#include "jfr/utilities/jfrTime.hpp"
|
||||||
|
#include "memory/allocation.hpp"
|
||||||
|
#include "runtime/javaThread.hpp"
|
||||||
|
#include "runtime/mutex.hpp"
|
||||||
|
|
||||||
|
class JfrSampleMonitor : public StackObj {
|
||||||
|
private:
|
||||||
|
JfrThreadLocal* const _tl;
|
||||||
|
Monitor* const _sample_monitor;
|
||||||
|
mutable bool _waiting;
|
||||||
|
public:
|
||||||
|
JfrSampleMonitor(JfrThreadLocal* tl) :
|
||||||
|
_tl(tl), _sample_monitor(tl->sample_monitor()), _waiting(false) {
|
||||||
|
assert(tl != nullptr, "invariant");
|
||||||
|
assert(_sample_monitor != nullptr, "invariant");
|
||||||
|
_sample_monitor->lock_without_safepoint_check();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool is_waiting() const {
|
||||||
|
assert_lock_strong(_sample_monitor);
|
||||||
|
_waiting = _tl->sample_state() == WAITING_FOR_NATIVE_SAMPLE;
|
||||||
|
return _waiting;
|
||||||
|
}
|
||||||
|
|
||||||
|
void install_java_sample_request() {
|
||||||
|
assert_lock_strong(_sample_monitor);
|
||||||
|
assert(_waiting, "invariant");
|
||||||
|
assert(_tl->sample_state() == WAITING_FOR_NATIVE_SAMPLE, "invariant");
|
||||||
|
JfrSampleRequest request;
|
||||||
|
request._sample_ticks = JfrTicks::now();
|
||||||
|
_tl->set_sample_request(request);
|
||||||
|
_tl->set_sample_state(JAVA_SAMPLE);
|
||||||
|
_sample_monitor->notify_all();
|
||||||
|
}
|
||||||
|
|
||||||
|
~JfrSampleMonitor() {
|
||||||
|
assert_lock_strong(_sample_monitor);
|
||||||
|
if (!_waiting) {
|
||||||
|
_tl->set_sample_state(NO_SAMPLE);
|
||||||
|
_sample_monitor->notify_all();
|
||||||
|
}
|
||||||
|
_sample_monitor->unlock();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // SHARE_JFR_PERIODIC_SAMPLING_JFRSAMPLEMONITOR_HPP
|
@ -48,10 +48,11 @@ enum JfrSampleResult {
|
|||||||
};
|
};
|
||||||
|
|
||||||
enum JfrSampleRequestType {
|
enum JfrSampleRequestType {
|
||||||
NO_SAMPLE = 0,
|
NO_SAMPLE,
|
||||||
NATIVE_SAMPLE = 1,
|
JAVA_SAMPLE,
|
||||||
JAVA_SAMPLE = 2,
|
NATIVE_SAMPLE,
|
||||||
NOF_SAMPLE_TYPES
|
WAITING_FOR_NATIVE_SAMPLE,
|
||||||
|
NOF_SAMPLE_STATES
|
||||||
};
|
};
|
||||||
|
|
||||||
struct JfrSampleRequest {
|
struct JfrSampleRequest {
|
||||||
|
@ -24,6 +24,7 @@
|
|||||||
|
|
||||||
#include "jfr/metadata/jfrSerializer.hpp"
|
#include "jfr/metadata/jfrSerializer.hpp"
|
||||||
#include "jfr/recorder/service/jfrOptionSet.hpp"
|
#include "jfr/recorder/service/jfrOptionSet.hpp"
|
||||||
|
#include "jfr/periodic/sampling/jfrSampleMonitor.hpp"
|
||||||
#include "jfr/periodic/sampling/jfrSampleRequest.hpp"
|
#include "jfr/periodic/sampling/jfrSampleRequest.hpp"
|
||||||
#include "jfr/periodic/sampling/jfrThreadSampling.hpp"
|
#include "jfr/periodic/sampling/jfrThreadSampling.hpp"
|
||||||
#include "jfr/periodic/sampling/jfrThreadSampler.hpp"
|
#include "jfr/periodic/sampling/jfrThreadSampler.hpp"
|
||||||
@ -230,38 +231,41 @@ void JfrSamplerThread::task_stacktrace(JfrSampleRequestType type, JavaThread** l
|
|||||||
JavaThread* start = nullptr;
|
JavaThread* start = nullptr;
|
||||||
elapsedTimer sample_time;
|
elapsedTimer sample_time;
|
||||||
sample_time.start();
|
sample_time.start();
|
||||||
{
|
ThreadsListHandle tlh;
|
||||||
MutexLocker tlock(Threads_lock);
|
// Resolve a sample session relative start position index into the thread list array.
|
||||||
ThreadsListHandle tlh;
|
// In cases where the last sampled thread is null or not-null but stale, find_index() returns -1.
|
||||||
// Resolve a sample session relative start position index into the thread list array.
|
_cur_index = tlh.list()->find_index_of_JavaThread(*last_thread);
|
||||||
// In cases where the last sampled thread is null or not-null but stale, find_index() returns -1.
|
JavaThread* current = _cur_index != -1 ? *last_thread : nullptr;
|
||||||
_cur_index = tlh.list()->find_index_of_JavaThread(*last_thread);
|
|
||||||
JavaThread* current = _cur_index != -1 ? *last_thread : nullptr;
|
|
||||||
|
|
||||||
while (num_samples < sample_limit) {
|
while (num_samples < sample_limit) {
|
||||||
current = next_thread(tlh.list(), start, current);
|
current = next_thread(tlh.list(), start, current);
|
||||||
if (current == nullptr) {
|
if (current == nullptr) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (is_excluded(current)) {
|
if (is_excluded(current)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (start == nullptr) {
|
if (start == nullptr) {
|
||||||
start = current; // remember the thread where we started to attempt sampling
|
start = current; // remember the thread where we started to attempt sampling
|
||||||
}
|
}
|
||||||
bool success;
|
bool success;
|
||||||
if (JAVA_SAMPLE == type) {
|
if (JAVA_SAMPLE == type) {
|
||||||
success = sample_java_thread(current);
|
success = sample_java_thread(current);
|
||||||
} else {
|
} else {
|
||||||
assert(type == NATIVE_SAMPLE, "invariant");
|
assert(type == NATIVE_SAMPLE, "invariant");
|
||||||
success = sample_native_thread(current);
|
success = sample_native_thread(current);
|
||||||
}
|
}
|
||||||
if (success) {
|
if (success) {
|
||||||
num_samples++;
|
num_samples++;
|
||||||
}
|
}
|
||||||
|
if (SafepointSynchronize::is_at_safepoint()) {
|
||||||
|
// For _thread_in_native, we cannot get the Threads_lock.
|
||||||
|
// For _thread_in_Java, well, there are none.
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
*last_thread = current; // remember the thread we last attempted to sample
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
*last_thread = current; // remember the thread we last attempted to sample
|
||||||
sample_time.stop();
|
sample_time.stop();
|
||||||
log_trace(jfr)("JFR thread sampling done in %3.7f secs with %d java %d native samples",
|
log_trace(jfr)("JFR thread sampling done in %3.7f secs with %d java %d native samples",
|
||||||
sample_time.seconds(), type == JAVA_SAMPLE ? num_samples : 0, type == NATIVE_SAMPLE ? num_samples : 0);
|
sample_time.seconds(), type == JAVA_SAMPLE ? num_samples : 0, type == NATIVE_SAMPLE ? num_samples : 0);
|
||||||
@ -338,17 +342,32 @@ bool JfrSamplerThread::sample_native_thread(JavaThread* jt) {
|
|||||||
|
|
||||||
SafepointMechanism::arm_local_poll_release(jt);
|
SafepointMechanism::arm_local_poll_release(jt);
|
||||||
|
|
||||||
// Barriers needed to keep the next read of thread state from floating up.
|
// Take the Threads_lock for two purposes:
|
||||||
if (UseSystemMemoryBarrier) {
|
// 1) Avoid sampling through a safepoint which could result
|
||||||
SystemMemoryBarrier::emit();
|
// in touching oops in case of virtual threads.
|
||||||
} else {
|
// 2) Prevent JFR from issuing an epoch rotation while the sampler thread
|
||||||
OrderAccess::storeload();
|
// is actively processing a thread in native, as both threads are now
|
||||||
|
// outside the safepoint protocol.
|
||||||
|
|
||||||
|
// OrderAccess::fence() as part of acquiring the lock prevents loads from floating up.
|
||||||
|
JfrMutexTryLock threads_lock(Threads_lock);
|
||||||
|
|
||||||
|
if (!threads_lock.acquired() || !jt->has_last_Java_frame()) {
|
||||||
|
// Remove the native sample request and release the potentially waiting thread.
|
||||||
|
JfrSampleMonitor jsm(tl);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (jt->thread_state() != _thread_in_native || !jt->has_last_Java_frame()) {
|
if (jt->thread_state() != _thread_in_native) {
|
||||||
MonitorLocker lock(tl->sample_monitor(), Monitor::_no_safepoint_check_flag);
|
assert_lock_strong(Threads_lock);
|
||||||
tl->set_sample_state(NO_SAMPLE);
|
JfrSampleMonitor jsm(tl);
|
||||||
lock.notify_all();
|
if (jsm.is_waiting()) {
|
||||||
|
// The thread has already returned from native,
|
||||||
|
// now in _thread_in_vm and is waiting to be sampled.
|
||||||
|
// Convert the native sample request into a java sample request
|
||||||
|
// and let the thread process the ljf on its own.
|
||||||
|
jsm.install_java_sample_request();
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -28,6 +28,7 @@
|
|||||||
#include "code/nmethod.hpp"
|
#include "code/nmethod.hpp"
|
||||||
#include "interpreter/interpreter.hpp"
|
#include "interpreter/interpreter.hpp"
|
||||||
#include "jfr/jfrEvents.hpp"
|
#include "jfr/jfrEvents.hpp"
|
||||||
|
#include "jfr/periodic/sampling/jfrSampleMonitor.hpp"
|
||||||
#include "jfr/periodic/sampling/jfrSampleRequest.hpp"
|
#include "jfr/periodic/sampling/jfrSampleRequest.hpp"
|
||||||
#include "jfr/periodic/sampling/jfrThreadSampling.hpp"
|
#include "jfr/periodic/sampling/jfrThreadSampling.hpp"
|
||||||
#include "jfr/recorder/stacktrace/jfrStackTrace.hpp"
|
#include "jfr/recorder/stacktrace/jfrStackTrace.hpp"
|
||||||
@ -307,24 +308,6 @@ static void drain_enqueued_requests(const JfrTicks& now, JfrThreadLocal* tl, Jav
|
|||||||
assert(!tl->has_enqueued_requests(), "invariant");
|
assert(!tl->has_enqueued_requests(), "invariant");
|
||||||
}
|
}
|
||||||
|
|
||||||
class SampleMonitor : public StackObj {
|
|
||||||
private:
|
|
||||||
JfrThreadLocal* const _tl;
|
|
||||||
Monitor* const _sample_monitor;
|
|
||||||
public:
|
|
||||||
SampleMonitor(JfrThreadLocal* tl) : _tl(tl), _sample_monitor(tl->sample_monitor()) {
|
|
||||||
assert(tl != nullptr, "invariant");
|
|
||||||
assert(_sample_monitor != nullptr, "invariant");
|
|
||||||
_sample_monitor->lock_without_safepoint_check();
|
|
||||||
}
|
|
||||||
~SampleMonitor() {
|
|
||||||
assert_lock_strong(_sample_monitor);
|
|
||||||
_tl->set_sample_state(NO_SAMPLE);
|
|
||||||
_sample_monitor->notify_all();
|
|
||||||
_sample_monitor->unlock();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Only entered by the JfrSampler thread.
|
// Only entered by the JfrSampler thread.
|
||||||
bool JfrThreadSampling::process_native_sample_request(JfrThreadLocal* tl, JavaThread* jt, Thread* sampler_thread) {
|
bool JfrThreadSampling::process_native_sample_request(JfrThreadLocal* tl, JavaThread* jt, Thread* sampler_thread) {
|
||||||
assert(tl != nullptr, "invairant");
|
assert(tl != nullptr, "invairant");
|
||||||
@ -334,7 +317,9 @@ bool JfrThreadSampling::process_native_sample_request(JfrThreadLocal* tl, JavaTh
|
|||||||
assert(tl == jt->jfr_thread_local(), "invariant");
|
assert(tl == jt->jfr_thread_local(), "invariant");
|
||||||
assert(jt != sampler_thread, "only asynchronous processing of native samples");
|
assert(jt != sampler_thread, "only asynchronous processing of native samples");
|
||||||
assert(jt->has_last_Java_frame(), "invariant");
|
assert(jt->has_last_Java_frame(), "invariant");
|
||||||
assert(tl->sample_state() == NATIVE_SAMPLE, "invariant");
|
assert(tl->sample_state() >= NATIVE_SAMPLE, "invariant");
|
||||||
|
|
||||||
|
assert_lock_strong(Threads_lock);
|
||||||
|
|
||||||
const JfrTicks start_time = JfrTicks::now();
|
const JfrTicks start_time = JfrTicks::now();
|
||||||
|
|
||||||
@ -342,7 +327,7 @@ bool JfrThreadSampling::process_native_sample_request(JfrThreadLocal* tl, JavaTh
|
|||||||
traceid sid;
|
traceid sid;
|
||||||
|
|
||||||
{
|
{
|
||||||
SampleMonitor sm(tl);
|
JfrSampleMonitor sm(tl);
|
||||||
|
|
||||||
// Because the thread was in native, it is in a walkable state, because
|
// Because the thread was in native, it is in a walkable state, because
|
||||||
// it will hit a safepoint poll on the way back from native. To ensure timely
|
// it will hit a safepoint poll on the way back from native. To ensure timely
|
||||||
@ -384,10 +369,14 @@ void JfrThreadSampling::process_sample_request(JavaThread* jt) {
|
|||||||
for (;;) {
|
for (;;) {
|
||||||
const int sample_state = tl->sample_state();
|
const int sample_state = tl->sample_state();
|
||||||
if (sample_state == NATIVE_SAMPLE) {
|
if (sample_state == NATIVE_SAMPLE) {
|
||||||
|
tl->set_sample_state(WAITING_FOR_NATIVE_SAMPLE);
|
||||||
// Wait until stack trace is processed.
|
// Wait until stack trace is processed.
|
||||||
ml.wait();
|
ml.wait();
|
||||||
} else if (sample_state == JAVA_SAMPLE) {
|
} else if (sample_state == JAVA_SAMPLE) {
|
||||||
tl->enqueue_request();
|
tl->enqueue_request();
|
||||||
|
} else if (sample_state == WAITING_FOR_NATIVE_SAMPLE) {
|
||||||
|
// Handle spurious wakeups. Again wait until stack trace is processed.
|
||||||
|
ml.wait();
|
||||||
} else {
|
} else {
|
||||||
// State has been processed.
|
// State has been processed.
|
||||||
break;
|
break;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user