diff --git a/agent/pom.xml b/agent/pom.xml
index 03dfc8e..cbe2f9a 100644
--- a/agent/pom.xml
+++ b/agent/pom.xml
@@ -52,7 +52,7 @@
agent.net.bytebuddy
- false
+ true
diff --git a/agent/src/main/java/io/type/pollution/agent/Agent.java b/agent/src/main/java/io/type/pollution/agent/Agent.java
index 3d4d5f2..cb1d374 100644
--- a/agent/src/main/java/io/type/pollution/agent/Agent.java
+++ b/agent/src/main/java/io/type/pollution/agent/Agent.java
@@ -21,6 +21,9 @@
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
@@ -40,6 +43,8 @@ public class Agent {
private static final Long REPORT_INTERVAL_SECS = Long.getLong("io.type.pollution.report.interval");
private static final boolean ENABLE_LAMBDA_INSTRUMENTATION = Boolean.getBoolean("io.type.pollution.lambda");
+ private static final Set disabledClassLoaders = Collections.synchronizedSet(new HashSet<>());
+
public static void premain(String agentArgs, Instrumentation inst) {
if (ENABLE_FULL_STACK_TRACES) {
TraceInstanceOf.startMetronome(FULL_STACK_TRACES_SAMPLING_PERIOD_MS);
@@ -78,31 +83,43 @@ public static void premain(String agentArgs, Instrumentation inst) {
typeDescription,
classLoader,
module,
- protectionDomain) -> builder
- .visit(TypeConstantAdjustment.INSTANCE)
- .visit(new AsmVisitorWrapper.AbstractBase() {
-
- @Override
- public int mergeWriter(int flags) {
- return flags | ClassWriter.COMPUTE_FRAMES;
- }
-
- @Override
- public int mergeReader(int flags) {
- return flags;
- }
-
- @Override
- public net.bytebuddy.jar.asm.ClassVisitor wrap(TypeDescription instrumentedType,
- net.bytebuddy.jar.asm.ClassVisitor classVisitor,
- Implementation.Context implementationContext,
- TypePool typePool,
- FieldList fields,
- MethodList> methods,
- int writerFlags, int readerFlags) {
- return new ByteBuddyUtils.ByteBuddyTypePollutionClassVisitor(net.bytebuddy.jar.asm.Opcodes.ASM9, classVisitor);
- }
- })).installOn(inst);
+ protectionDomain) -> {
+ if (classLoader != null) {
+ // in gradle test runners, there are class loaders that don't include the agent classes
+ try {
+ classLoader.loadClass(TraceInstanceOf.class.getName());
+ } catch (ClassNotFoundException e) {
+ disabledClassLoaders.add(classLoader.toString());
+ return builder;
+ }
+ }
+
+ return builder
+ .visit(TypeConstantAdjustment.INSTANCE)
+ .visit(new AsmVisitorWrapper.AbstractBase() {
+
+ @Override
+ public int mergeWriter(int flags) {
+ return flags | ClassWriter.COMPUTE_FRAMES;
+ }
+
+ @Override
+ public int mergeReader(int flags) {
+ return flags;
+ }
+
+ @Override
+ public net.bytebuddy.jar.asm.ClassVisitor wrap(TypeDescription instrumentedType,
+ net.bytebuddy.jar.asm.ClassVisitor classVisitor,
+ Implementation.Context implementationContext,
+ TypePool typePool,
+ FieldList fields,
+ MethodList> methods,
+ int writerFlags, int readerFlags) {
+ return new ByteBuddyUtils.ByteBuddyTypePollutionClassVisitor(net.bytebuddy.jar.asm.Opcodes.ASM9, classVisitor);
+ }
+ });
+ }).installOn(inst);
}
private static void printFinalReport() {
@@ -125,6 +142,9 @@ private static CharSequence reportOf(Collection SAMPLING_TICK_UPDATER =
AtomicLongFieldUpdater.newUpdater(TraceCounter.class, "lastSamplingTick");
- private final Class clazz;
+ final Class clazz;
private volatile long lastSamplingTick = System.nanoTime();
private final ConcurrentHashMap traces = new ConcurrentHashMap<>();
private static final ThreadLocal TRACE = ThreadLocal.withInitial(Trace::new);
diff --git a/agent/src/main/java/io/type/pollution/agent/TypeCheckThrashEvent.java b/agent/src/main/java/io/type/pollution/agent/TypeCheckThrashEvent.java
new file mode 100644
index 0000000..821a6bd
--- /dev/null
+++ b/agent/src/main/java/io/type/pollution/agent/TypeCheckThrashEvent.java
@@ -0,0 +1,27 @@
+package io.type.pollution.agent;
+
+import jdk.jfr.Category;
+import jdk.jfr.Description;
+import jdk.jfr.Enabled;
+import jdk.jfr.Event;
+import jdk.jfr.Label;
+import jdk.jfr.Name;
+import jdk.jfr.StackTrace;
+
+@Name(TypeCheckThrashEvent.NAME)
+@Label("Type check cache thrashing")
+@Category("Type check")
+@Description("An interface type check is invalidating a previously cached type check result. If this happens often, it can lead to thrashing and contention of the cache field.")
+@StackTrace
+@Enabled(false)
+public class TypeCheckThrashEvent extends Event {
+ static final String NAME = "io.type.pollution.agent.TypeCheckThrashEvent";
+
+ @Label("Concrete type")
+ @Description("Concrete class of the object that was type checked (the thrashed cache field is stored in this class)")
+ public String concreteClass;
+
+ @Label("Interface type")
+ @Description("Interface that this object was checked against (this is the type that is stored in the thrashed cache field)")
+ public String interfaceClass;
+}