我正在尝试使用 ByteBuddy 编写一个代理,它将拦截
java.lang.OutOfMemoryError
的构造并在那时调用我的静态方法。示例代码:
public class OOMAgent {
private static final Method HANDLE_OOM;
static {
try {
HANDLE_OOM = OOMAgent.class.getDeclaredMethod("handleOOM");
} catch (NoSuchMethodException e) {
throw new EvitaInternalError("!!! OOMAgent initialization failed !!!", e);
}
}
public static void premain(String agentArgs, Instrumentation inst) {
if (HANDLE_OOM != null) {
new AgentBuilder.Default()
.type(named("java.lang.OutOfMemoryError"))
.transform(
(builder, typeDescription, classLoader, javaModule, protectionDomain) -> builder
.constructor(any())
.intercept(SuperMethodCall.INSTANCE.andThen(MethodCall.invoke(HANDLE_OOM)))
)
.installOn(inst);
}
}
public static void handleOOM() {
System.out.println("!!! OOM !!!");
}
}
我正在使用 Java,ByteBuddy 1.14.14,并且我的
MANIFEST.MF
文件中有以下信息
Can-Redefine-Classes: true
Can-Retransform-Classes: true
Premain-Class: io.evitadb.externalApi.observability.agent.OOMAgent
OOMAgent
类被正确调用并且代理被“安装”(没有抛出异常)。但当异常发生时,handleOOM
方法不会被调用。
您能发现一些明显错误的地方,或者给我一些关于如何“调试”这样的代理以找出问题所在的提示吗?
也许 Java 类受到某种保护?我也在使用 Java 9 模块系统 - 可能存在某种“可见性”问题吗?
更新#1:
我还尝试了这篇文章中的提示:检测引导/扩展类加载器加载的类的正确方法是什么?
这给了我更详细的日志,让我认为该类已被转换:
[Byte Buddy] BEFORE_INSTALL net.bytebuddy.agent.builder.AgentBuilder$Default$ExecutingTransformer$ByteBuddy$ModuleSupport@22e357dc on sun.instrument.InstrumentationImpl@49912c99
[Byte Buddy] TRANSFORM java.lang.OutOfMemoryError [null, module java.base, Thread[main,5,main], loaded=true]
[Byte Buddy] INSTALL net.bytebuddy.agent.builder.AgentBuilder$Default$ExecutingTransformer$ByteBuddy$ModuleSupport@22e357dc on sun.instrument.InstrumentationImpl@49912c99
但是构造函数仍然没有被拦截。
更新#2:
当我手动构造 OutOfMemoryError 时,出现以下异常:
java.lang.NoClassDefFoundError: io/evitadb/externalApi/observability/agent/OOMAgent
at java.base/java.lang.OutOfMemoryError.<init>(OutOfMemoryError.java)
at ....
所以看起来类毕竟被转换了,但是 JVM 使用了不同的方式来构造它以避免构造函数调用?!而且转换后的类看不到我的代理类 - 可能是由于位于引导类加载器中。
您的意思是这样吗?
import net.bytebuddy.agent.ByteBuddyAgent;
import net.bytebuddy.agent.builder.AgentBuilder;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.asm.Advice.OnMethodEnter;
import net.bytebuddy.dynamic.loading.ClassInjector;
import java.lang.instrument.Instrumentation;
import static net.bytebuddy.matcher.ElementMatchers.*;
public class BootstrapOOMAgent {
public static void main(String[] args) {
premain("dummy", ByteBuddyAgent.install());
throw new OutOfMemoryError("uh-oh!");
}
public static void premain(String arg, Instrumentation instrumentation) {
ClassInjector.UsingUnsafe.Factory factory = ClassInjector.UsingUnsafe.Factory.resolve(instrumentation);
AgentBuilder agentBuilder = new AgentBuilder.Default();
agentBuilder = agentBuilder.with(new AgentBuilder.InjectionStrategy.UsingUnsafe.OfFactory(factory));
agentBuilder
.disableClassFormatChanges()
.with(AgentBuilder.RedefinitionStrategy.RETRANSFORMATION)
.ignore(none())
.ignore(nameStartsWith("net.bytebuddy."))
.type(is(OutOfMemoryError.class))
.transform((builder, typeDescription, classLoader, module, protectionDomain) -> builder
.visit(
Advice
.to(MyAdvice.class)
.on(isConstructor())
))
.installOn(instrumentation);
}
public static class MyAdvice {
@OnMethodEnter
public static boolean before() {
System.out.println("!!! OOM !!!");
return true;
}
}
}
控制台日志:
!!! OOM !!!
Exception in thread "main" java.lang.OutOfMemoryError: uh-oh!
at BootstrapOOMAgent.main(BootstrapOOMAgent.java:37)
快速改编自我的回答。
在JDoodle上尝试一下。