使用 JVM 代理拦截 OutOfMemoryErrors

问题描述 投票:0回答:1

我正在尝试使用 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 使用了不同的方式来构造它以避免构造函数调用?!而且转换后的类看不到我的代理类 - 可能是由于位于引导类加载器中。

byte-buddy javaagents
1个回答
0
投票

您的意思是这样吗?

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上尝试一下。

© www.soinside.com 2019 - 2024. All rights reserved.