FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

Wire reference chains into profiler lifecycle and JNI API by jbachorik · Pull Request #798 · DataDog/java-profiler · GitHub

Repository navigation

Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
8608b08
Add JFR event types for reference chains
jbachorik Sep 17, 2026
a2b0120
Wire reference chains into the profiler lifecycle and JNI API
jbachorik Sep 17, 2026
2449249
Address review: merge chain hops, trim comments, drop doc/jira refs
jbachorik Sep 18, 2026
e9e0d69
Drop remaining Jira and cross-layer test references
jbachorik Sep 18, 2026
2971532
livenessTracker: urgency counters, OOM guards, epoch accounting
jbachorik Sep 28, 2026
216abc9
misc: objectSampler start order, dump-failure test log, Java seams pa…
jbachorik Sep 28, 2026
f71569d
referenceChains: defer resolved-chain invalidation off the FollowRefe…
jbachorik Sep 30, 2026
8a0b995
referenceChains tests: PES/ClassTagAllocator coverage, fix cacheChain…
jbachorik Sep 30, 2026
4169c73
objectSampler: roll back on LivenessTracker start failure
jbachorik Oct 6, 2026
97ecc3c
javaApi: release stubs for debug test seams, null-check jvmti
jbachorik Oct 6, 2026
af7af58
referenceChains: batch deferred invalidations, cache probe start
jbachorik Oct 6, 2026
d21eb08
tests: cover urgency backstop and account_epoch, tidy PES tests
jbachorik Oct 6, 2026
e8c79ca
Trim comments and test-seam Javadocs
jbachorik Oct 6, 2026
753deda
sphinx: address review feedback on PR #798
jbachorik Oct 6, 2026
0668fa1
sphinx: address review feedback on PR #798
jbachorik Oct 6, 2026
dc36a90
Trim review-fix comments
jbachorik Oct 7, 2026
660f241
sphinx: address review feedback on PR #798
jbachorik Oct 7, 2026
7802e49
Fix urgency check comment
jbachorik Oct 7, 2026
4fcba24
Address deep review findings on reference chains
jbachorik Oct 7, 2026
File filter

Filter by extension

Filter by extension .cpp  (10) .h  (5) .inc  (2) .java  (1) All 4 file types selected
Viewed files
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Unified
Split
Hide whitespace
Diff view
Unified
Split
Hide whitespace
7 changes: 7 additions & 0 deletions ddprof-lib/src/main/cpp/counters.h
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,13 @@
* and re-emits it on a later dump while the leak candidate is still \
* live. */ \
X(REFERENCE_CHAIN_WRITE_DROPPED, "reference_chain_write_dropped") \
/* Deferred chain invalidations that overflowed the pending buffer and \
* forced a full _resolved_chains clear on the next drain. */ \
X(REFERENCE_CHAIN_PENDING_INVALIDATIONS_OVERFLOWED, "reference_chain_pending_invalidations_overflowed") \
/* LivenessTracker urgency boost: admissions below the table cap vs. \
* admissions that hit the cap and fell back to the subsample ratio. */ \
X(LIVENESS_URGENT_BOOST_ADMITS, "liveness_urgent_boost_admits") \
X(LIVENESS_URGENT_BOOST_BACKED_OFF, "liveness_urgent_boost_backed_off") \
/* FrontierTable's own calloc/realloc-backed storage (referenceChains.cpp) - \
* outside NMT's visibility since it bypasses os::malloc, so this is the only \
* way to attribute its native RSS contribution. */ \
Expand Down
294 changes: 294 additions & 0 deletions ddprof-lib/src/main/cpp/javaApi.cpp
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -1104,6 +1104,300 @@ Java_com_datadoghq_profiler_JavaProfiler_dumpContext(JNIEnv* env, jclass unused)
TEST_LOG("===> Context: tid:%lu, spanId=%lu, rootSpanId=%lu", OS::threadId(), spanId, rootSpanId);
}

// LivenessTracker/ReferenceChainTracker test seams. They mutate real tracker
// state, so they are only implemented in DEBUG builds; release builds get the
// no-op stubs below.
#ifdef DEBUG
#include "livenessTracker.h"
#include "referenceChains.h"
#include <vector>

extern "C" DLLEXPORT jboolean JNICALL
Java_com_datadoghq_profiler_JavaProfiler_setGcGenerationsEnabled0(
JNIEnv *env, jclass unused, jboolean enabled) {
ProfiledThread::initCurrentThreadSignalSafe();
LivenessTracker::instance()->setGcGenerationsForTest(enabled);
Comment thread
jbachorik marked this conversation as resolved.
return JNI_TRUE;
}

extern "C" DLLEXPORT void JNICALL
Java_com_datadoghq_profiler_JavaProfiler_seedKlassPopulationSample0(
JNIEnv *env, jclass unused, jint klassId, jint count, jlong epoch) {
ProfiledThread::initCurrentThreadSignalSafe();
int slot;
bool created;
LivenessTracker::instance()->klassPopulationRecordForTest(
(u32)klassId, (u16)count, (u64)epoch, &slot, &created);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Besides the (u16) truncation (the callee takes u32, so the cast isn't needed), this passes out_evicted = nullptr. When the population table is full, recordKlassPopulationSampleLocked() evicts the LRU entry and memsets its representatives[], so those weak global refs leak. tidTrendRecordForTest()'s create path has the same issue. It's debug-only, but it's easy to collect the evicted refs and DeleteWeakGlobalRef them here like foldKlassCountsLocked() does.

}

// See tidTrendRecordForTest() (livenessTracker.h) for the tid requirements.
extern "C" DLLEXPORT void JNICALL
Java_com_datadoghq_profiler_JavaProfiler_seedTidTrendSample0(
JNIEnv *env, jclass unused, jint klassId, jint tid, jint count,
jlong epoch) {
ProfiledThread::initCurrentThreadSignalSafe();
LivenessTracker::instance()->tidTrendRecordForTest(
(u32)klassId, (jint)tid, (u32)count, (u64)epoch);
}

// Makes `representative` klassId's leak-candidate representative, so a seeded
// population sample and a tagged root can drive pollWatchedTargets() without
// the real sampler or walk. The tracker owns the weak global ref created here.
extern "C" DLLEXPORT void JNICALL
Java_com_datadoghq_profiler_JavaProfiler_setKlassPopulationRepresentativeForTest0(
JNIEnv *env, jclass unused, jint klassId, jobject representative) {
ProfiledThread::initCurrentThreadSignalSafe();
jweak rep = env->NewWeakGlobalRef(representative);
LivenessTracker::instance()->klassPopulationSetRepresentativeForTest(
env, (u32)klassId, rep);
}

extern "C" DLLEXPORT void JNICALL
Java_com_datadoghq_profiler_JavaProfiler_resetKlassPopulationForTest0(
JNIEnv *env, jclass unused) {
ProfiledThread::initCurrentThreadSignalSafe();
LivenessTracker::instance()->klassPopulationResetForTest();
}

extern "C" DLLEXPORT jintArray JNICALL
Java_com_datadoghq_profiler_JavaProfiler_selectLeakCandidateKlassIds0(
JNIEnv *env, jclass unused) {
ProfiledThread::initCurrentThreadSignalSafe();
KlassCandidate candidates[5];
int n = LivenessTracker::instance()->selectLeakCandidates(candidates, 5);
jintArray result = env->NewIntArray(n);
if (result == nullptr || n == 0) {
return result;
}
jint ids[5];
for (int i = 0; i < n; i++) {
ids[i] = (jint)candidates[i].klass_id;
}
env->SetIntArrayRegion(result, 0, n, ids);
return result;
}

extern "C" DLLEXPORT jlong JNICALL
Java_com_datadoghq_profiler_JavaProfiler_tagAsReferenceChainRoot0(
JNIEnv *env, jclass unused, jobject target) {
ProfiledThread::initCurrentThreadSignalSafe();
jvmtiEnv *jvmti = VM::jvmti();
if (jvmti == nullptr) {
return 0;
}
return ReferenceChainTracker::instance()->tagAsRootForTest(jvmti, env,
target);
}

extern "C" DLLEXPORT jboolean JNICALL
Java_com_datadoghq_profiler_JavaProfiler_runReferenceChainPass0(
JNIEnv *env, jclass unused) {
ProfiledThread::initCurrentThreadSignalSafe();
jvmtiEnv *jvmti = VM::jvmti();
if (jvmti == nullptr) {
return JNI_FALSE;
}
return ReferenceChainTracker::instance()->runPassSerialized(jvmti, env);
}

extern "C" DLLEXPORT void JNICALL
Java_com_datadoghq_profiler_JavaProfiler_pollReferenceChainTargets0(
JNIEnv *env, jclass unused) {
ProfiledThread::initCurrentThreadSignalSafe();
jvmtiEnv *jvmti = VM::jvmti();
if (jvmti == nullptr) {
return;
}
ReferenceChainTracker::instance()->finishLoopIterationSerialized(jvmti, env);
}

extern "C" DLLEXPORT jint JNICALL
Java_com_datadoghq_profiler_JavaProfiler_drainReferenceChainEventCount0(
JNIEnv *env, jclass unused) {
ProfiledThread::initCurrentThreadSignalSafe();
std::vector<ReferenceChainEvent> events;
ReferenceChainTracker::instance()->drainPendingChainEvents(&events);
return (jint)events.size();
}

extern "C" DLLEXPORT void JNICALL
Java_com_datadoghq_profiler_JavaProfiler_resetReferenceChainSearchForTest0(
JNIEnv *env, jclass unused) {
ProfiledThread::initCurrentThreadSignalSafe();
jvmtiEnv *jvmti = VM::jvmti();
if (jvmti == nullptr) {
return;
}
ReferenceChainTracker::instance()->resetSearchStateForTest(jvmti, env);
}

// Reads target's existing tag without tagging it. Return values are described
// at pendingExpandPositionForTest().
extern "C" DLLEXPORT jlong JNICALL
Java_com_datadoghq_profiler_JavaProfiler_getReferenceChainPendingPositionForTest0(
JNIEnv *env, jclass unused, jobject target) {
ProfiledThread::initCurrentThreadSignalSafe();
jvmtiEnv *jvmti = VM::jvmti();
if (jvmti == nullptr || target == nullptr) {
return -2;
}
jlong tag = 0;
jvmtiError err = jvmti->GetTag(target, &tag);
if (err != JVMTI_ERROR_NONE) {
return -2;
}
return (jlong)ReferenceChainTracker::instance()->pendingExpandPositionForTest(
Comment thread
jbachorik marked this conversation as resolved.
tag);
}

extern "C" DLLEXPORT jlong JNICALL
Java_com_datadoghq_profiler_JavaProfiler_getReferenceChainPendingSizeForTest0(
JNIEnv *env, jclass unused) {
ProfiledThread::initCurrentThreadSignalSafe();
return (jlong)ReferenceChainTracker::instance()->pendingExpandSizeForTest();
}

// Seeds one heap-floor sample for secondsToOOM() without waiting for a GC.
// Timestamps are only compared with each other, so any increasing sequence works.
extern "C" DLLEXPORT void JNICALL
Java_com_datadoghq_profiler_JavaProfiler_heapFloorRecordForTest0(
JNIEnv *env, jclass unused, jlong usedBytes, jlong timestampNs) {
ProfiledThread::initCurrentThreadSignalSafe();
LivenessTracker::instance()->heapFloorRecordForTest((u64)usedBytes,
(u64)timestampNs);
}

// Overrides the max heap size secondsToOOM() projects against.
extern "C" DLLEXPORT void JNICALL
Java_com_datadoghq_profiler_JavaProfiler_setMaxHeapBytesForTest0(
JNIEnv *env, jclass unused, jlong maxHeapBytes) {
ProfiledThread::initCurrentThreadSignalSafe();
LivenessTracker::instance()->setMaxHeapBytesForTest((jlong)maxHeapBytes);
}

// Stops real GCs from recording heap-floor samples while a test seeds them.
extern "C" DLLEXPORT void JNICALL
Java_com_datadoghq_profiler_JavaProfiler_setHeapFloorRecordingForTest0(
JNIEnv *env, jclass unused, jboolean enabled) {
ProfiledThread::initCurrentThreadSignalSafe();
LivenessTracker::instance()->setHeapFloorRecordingForTest(enabled == JNI_TRUE);
}

// Whether the search-restart gate would currently allow a new search.
extern "C" DLLEXPORT jboolean JNICALL
Java_com_datadoghq_profiler_JavaProfiler_shouldRunPassForTest0(JNIEnv *env,
jclass unused) {
ProfiledThread::initCurrentThreadSignalSafe();
return ReferenceChainTracker::instance()->shouldRunPassForTest(
OS::nanotime())
? JNI_TRUE
: JNI_FALSE;
}

// Lets a test wait for a pass that started after it created an object.
extern "C" DLLEXPORT jint JNICALL
Java_com_datadoghq_profiler_JavaProfiler_referenceChainPassesRunForTest0(
JNIEnv *env, jclass unused) {
ProfiledThread::initCurrentThreadSignalSafe();
return (jint)ReferenceChainTracker::instance()->passesRun();
}

#else // !DEBUG

// Release builds: no-op stubs so the Java natives still link.
extern "C" DLLEXPORT jboolean JNICALL
Java_com_datadoghq_profiler_JavaProfiler_setGcGenerationsEnabled0(
JNIEnv *env, jclass unused, jboolean enabled) {
return JNI_FALSE;
}

extern "C" DLLEXPORT void JNICALL
Java_com_datadoghq_profiler_JavaProfiler_seedKlassPopulationSample0(
JNIEnv *env, jclass unused, jint klassId, jint count, jlong epoch) {}

extern "C" DLLEXPORT void JNICALL
Java_com_datadoghq_profiler_JavaProfiler_seedTidTrendSample0(
JNIEnv *env, jclass unused, jint klassId, jint tid, jint count,
jlong epoch) {}

extern "C" DLLEXPORT void JNICALL
Java_com_datadoghq_profiler_JavaProfiler_setKlassPopulationRepresentativeForTest0(
JNIEnv *env, jclass unused, jint klassId, jobject representative) {}

extern "C" DLLEXPORT void JNICALL
Java_com_datadoghq_profiler_JavaProfiler_resetKlassPopulationForTest0(
JNIEnv *env, jclass unused) {}

extern "C" DLLEXPORT jintArray JNICALL
Java_com_datadoghq_profiler_JavaProfiler_selectLeakCandidateKlassIds0(
JNIEnv *env, jclass unused) {
return env->NewIntArray(0);
}

extern "C" DLLEXPORT jlong JNICALL
Java_com_datadoghq_profiler_JavaProfiler_tagAsReferenceChainRoot0(
JNIEnv *env, jclass unused, jobject target) {
return 0;
}

extern "C" DLLEXPORT jboolean JNICALL
Java_com_datadoghq_profiler_JavaProfiler_runReferenceChainPass0(
JNIEnv *env, jclass unused) {
return JNI_FALSE;
}

extern "C" DLLEXPORT void JNICALL
Java_com_datadoghq_profiler_JavaProfiler_pollReferenceChainTargets0(
JNIEnv *env, jclass unused) {}

extern "C" DLLEXPORT jint JNICALL
Java_com_datadoghq_profiler_JavaProfiler_drainReferenceChainEventCount0(
JNIEnv *env, jclass unused) {
return 0;
}

extern "C" DLLEXPORT void JNICALL
Java_com_datadoghq_profiler_JavaProfiler_resetReferenceChainSearchForTest0(
JNIEnv *env, jclass unused) {}

extern "C" DLLEXPORT jlong JNICALL
Java_com_datadoghq_profiler_JavaProfiler_getReferenceChainPendingPositionForTest0(
JNIEnv *env, jclass unused, jobject target) {
return -2;
}

extern "C" DLLEXPORT jlong JNICALL
Java_com_datadoghq_profiler_JavaProfiler_getReferenceChainPendingSizeForTest0(
JNIEnv *env, jclass unused) {
return 0;
}

extern "C" DLLEXPORT void JNICALL
Java_com_datadoghq_profiler_JavaProfiler_heapFloorRecordForTest0(
JNIEnv *env, jclass unused, jlong usedBytes, jlong timestampNs) {}

extern "C" DLLEXPORT void JNICALL
Java_com_datadoghq_profiler_JavaProfiler_setMaxHeapBytesForTest0(
JNIEnv *env, jclass unused, jlong maxHeapBytes) {}

extern "C" DLLEXPORT void JNICALL
Java_com_datadoghq_profiler_JavaProfiler_setHeapFloorRecordingForTest0(
JNIEnv *env, jclass unused, jboolean enabled) {}

extern "C" DLLEXPORT jboolean JNICALL
Java_com_datadoghq_profiler_JavaProfiler_shouldRunPassForTest0(JNIEnv *env,
jclass unused) {
return JNI_FALSE;
}

extern "C" DLLEXPORT jint JNICALL
Java_com_datadoghq_profiler_JavaProfiler_referenceChainPassesRunForTest0(
JNIEnv *env, jclass unused) {
return 0;
}

#endif // DEBUG

// ---- Test-only reads of the current thread's OTEP record -----------------------------------
// Each reads the current carrier's record directly via ProfiledThread::current(), with no
// detach/attach (diagnostic-only, not on any signal-handler or hot write path).
Expand Down
Loading
Loading

Back | FazBrowse Home | New Git URL