【问题标题】:Java - Inject java agent in to running jvmJava - 将 java 代理注入到运行的 jvm
【发布时间】:2016-08-15 03:15:52
【问题描述】:

基本上,我正在尝试编写一些内容来列出 JVM 加载的每个类。我写的东西有效,但它只适用于它正在运行的 jvm。 我制作了一个 java 代理来动态注入另一个 JVM,但后来意识到我实际上并不知道如何注入它。 我如何实际将此代理发送到另一个 JVM? 有可能吗?

【问题讨论】:

标签: java jvm classloader javaagents


【解决方案1】:

动态代理需要声明一个agentmain(String, Instrumentation) 方法,该方法在目标VM 中附加时执行。您可以使用 tools.jar 依赖项,该依赖项(直到 Java 9)仅包含在 JDK 而不是 JRE 中。但是,您可以将代理程序与 JDK 捆绑在一起,然后从那里附加到 JVM。

最大的缺陷是不同 VM 的 API 不同;但是,您可以使用像 byte-buddy-agent 这样的库,其中包含针对不同 VM 的不同实现。附件可以使用:

ByteBuddyAgent.attach("my.jar", "my-pid");

这会将 my.jar 中包含的代理附加到 ID 为 my-id 的 Java 进程上。

【讨论】:

  • 是的。操作系统进程 ID。
  • 我尝试过这样做,但在测试中似乎从未调用过我的 agentmain 方法。
  • 进一步检查,agentmain 方法只有在我附加到运行它的 JVM 时才会被调用。
  • 被目标虚拟机调用。
  • 基本上我要做的是获取 Instrumentation 的实例。如果目标 vm 调用 agentmain,那是否就无法获取不同 JVM 的检测实例?
【解决方案2】:

代理可以用HotSpot Attach API注入。
在类路径上使用$JAVA_HOME/lib/tools.jar 运行以下 sn-p。

    VirtualMachine vm = VirtualMachine.attach(PID);
    try {
        vm.loadAgent(agentJar);
    } finally {
        vm.detach();
    }

您也可以使用我的命令行jattach 实用程序来执行此操作:

$ jattach PID load instrument false /path/to/agent.jar

请注意,为了支持动态附加,您的 Java 代理应在 MANIFEST.MF 中具有 agentmain 方法和 Agent-Class 属性。

【讨论】:

  • 代理有一个小问题。我假设将检测代理与目标 JVM 分开打包是最标准的做法。当我的agentmain() 被调用时,我试图通过调用Class.forName("pkg.name") 来查找JVM 类,但它总是返回NoClassDefFoundError。我已经在这里发布了问题(stackoverflow.com/questions/46523055/…。我有点困惑为什么代理,即使连接到 VM,也不能引用它的类。
  • 另外,您可以使用 jvm-attach,它是 jattach 的包装器,以编程方式进行检测,不需要 tools.jar 或 JDK
【解决方案3】:

据我从the comment 了解到,您对可以从另一个 Java 进程中检查远程 JVM 的东西感兴趣。如果是这种情况,那么您需要 Serviceability Agent 而不是 Java 代理。

Serviceability Agent API 允许您附加到另一个 JVM 进程、读取其内存、重构 VM 结构并以类似反射的方式检查远程对象。

这是一个示例工具,用于列出远程 JVM 加载的所有类:

import sun.jvm.hotspot.runtime.VM;
import sun.jvm.hotspot.tools.Tool;

public class ListRemoteClasses extends Tool {

    public static void main(String[] args) {
        new ListRemoteClasses().execute(args);
    }

    @Override
    public void run() {
        VM.getVM().getSystemDictionary().classesDo(klass -> {
            String className = klass.getName().asString().replace('/', '.');
            System.out.println(className);
        });
    }
}

如何运行:

java -cp $JAVA_HOME/lib/sa-jdi.jar:. ListRemoteClasses PID

【讨论】:

    【解决方案4】:

    如果不查看您编写的内容,很难提供帮助,但这只是为了通知java.lang.instrument 包中有一个名为 Instrumentation 接口 (public interface Instrumentation) 的类,它提供了检测 Java 所需的服务编程语言代码。

    此类提供的一个这样的方法是getInitiatedClasses,它返回一个包含所有已加载类的数组。

    查看文档here

    getInitiatedClasses

    Class[] getInitiatedClasses(ClassLoader 加载器)
    返回 loader 为其初始加载器的所有类的数组。如果 提供的加载器为空,由引导类启动的类 loader 被返回。

    参数: loader - 将返回其启动类列表的加载器 返回: 一个数组,其中包含 loader 作为初始加载器的所有类,如果没有则长度为零

    【讨论】:

    • 我知道这一点。问题是 jar 已经在运行,我需要一个 javaagent 来连接它。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多