Skip to content
Merged
Show file tree
Hide file tree
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
Original file line number Diff line number Diff line change
Expand Up @@ -26,16 +26,26 @@ class InstrumenterUnloadTest extends Specification {
, ["DD_API_KEY": API_KEY]
, new PrintStream(testOutput))

int unloadCount = 0
boolean canaryUnloaded = false
int unloadedInstrumentationCount = 0
new ByteArrayInputStream((testOutput.toByteArray())).eachLine {
System.out.println(it)
if (it =~ /(?i)unload.*Canary/) {
canaryUnloaded = true
}
if (it =~ /(?i)unload.* datadog.trace.instrumentation./) {
unloadCount++
unloadedInstrumentationCount++
}
}

if (!canaryUnloaded) {
System.out.println("WARNING: Canary class was not unloaded!")
}

then:
returnCode == 0
unloadCount > 0
// skip check if we couldn't even unload our Canary class, as that
// indicates full GC didn't happen enough to trigger any unloading
!canaryUnloaded || unloadedInstrumentationCount > 0
}
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,32 @@
package jvmbootstraptest;

import static java.util.concurrent.TimeUnit.MINUTES;

import datadog.trace.test.util.GCUtils;
import java.lang.management.ClassLoadingMXBean;
import java.lang.management.ManagementFactory;

public class UnloadingChecker {
public static void main(final String[] args) {
try {
GCUtils.awaitGC();
} catch (InterruptedException e) {
e.printStackTrace();
static class Canary {}

public static void main(final String[] args) throws Exception {
ClassLoadingMXBean classLoadingMXBean = ManagementFactory.getClassLoadingMXBean();
long initialUnloadCount = classLoadingMXBean.getUnloadedClassCount();

// load an isolated class which we know can be unloaded after a full GC
new IsolatingClassLoader().loadClass("jvmbootstraptest.UnloadingChecker$Canary");
Copy link
Copy Markdown
Contributor

@dougqh dougqh Jun 11, 2025

Choose a reason for hiding this comment

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

Nice. I was considering something similar inside GCUtils.
Specifically, I was thinking a PhantomReference to an isolated Class, then verify unloading via a ReferenceQueue.

That would at least provide some indication that classes were collection rather than a random Object.


long waitNanos = MINUTES.toNanos(2);
long startNanos = System.nanoTime();

while (System.nanoTime() - startNanos < waitNanos) {
try {
GCUtils.awaitGC();
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I would call directly System.gc() here
awaitGC is still fundamentally flawed because of weak reference

} catch (Throwable ignore) {
}
if (initialUnloadCount < classLoadingMXBean.getUnloadedClassCount()) {
break; // some class unloading has taken place, stop and check results
}
}
}
}
Loading