在程序启动后启动Java代理

28

在虚拟机启动后,是否有可能从同一虚拟机内插入javaagent?

例如,我们有一个名为myagent.jar的jar包代理,具有适当的元数据设置和已经实现的agentmain方法。现在用户程序调用了一个API调用,应该导致插入代理以便重新定义类。

它可行吗?如何做到?

4个回答

20

3
这句话的意思是:它并不旨在成为一个独立的教程,但对于高级Java开发人员来说,它是一个很好的示例。我个人没有问题。 - Alan Cabrera
2
链接已经失效。我在 WayBackMachine 上找到了它的存档:https://web.archive.org/web/20141014195801/http://dhruba.name/2010/02/07/creation-dynamic-loading-and-instrumentation-with-javaagents/ - 11101101b
该链接不再有效。您应该复制内容并在此粘贴。 - hrishikeshp19
2
网络存档链接足矣。 - Alan Cabrera
@AlanCabrera 我正在使用jdk 1.8。我已经尝试过oracle和openjdk,但我从未能够附加到jvm。该尝试会导致目标jvm中的线程转储,并在注入jvm时失败,显示以下错误信息: com.sun.tools.attach.AttachNotSupportedException: 无法打开套接字文件:目标进程未响应或HotSpot VM未加载 at sun.tools.attach.LinuxVirtualMachine.<init>(LinuxVirtualMachine.java:106) at sun.tools.attach.LinuxAttachProvider.attachVirtualMachine(LinuxAttachProvider.java:63) at com.sun.tools.attach.VirtualMachine.attach(VirtualMachine.java:208) at Main.main(Main.java:15) - Sam Thomas
我已经在CentOS和Ubuntu上尝试过了。两者都以相同的方式失败了。 - Sam Thomas

15

是的,你只需要将JVM进程ID传递给VirtualMachine.attach(String pid)方法,并加载代理jar包。 VirtualMachine类可以在JDK_HOME/lib/tools.jar文件中找到。以下是我在运行时激活代理的示例:

public static void attachGivenAgentToThisVM(String pathToAgentJar) {
  try {                                                                               
    String nameOfRunningVM = ManagementFactory.getRuntimeMXBean().getName();                                                   
    String pid = nameOfRunningVM.substring(0, nameOfRunningVM.indexOf('@'));                                                   
    VirtualMachine vm = VirtualMachine.attach(pid);                                                                            
    vm.loadAgent(pathToAgentJar, "");
    vm.detach();   
  } catch (Exception e) {
    e.printStackTrace();
  }
}                                                                                                            

1
感谢您的好回答。但是当我尝试相同的操作时,我遇到了一个异常 com.sun.tools.attach.AgentLoadException: 找不到代理 JAR 文件或没有 Agent-Class 属性 - madhu
1
我怀疑这是一个依赖问题,以下是在Maven中解决的方法:http://m.blog.csdn.net/blog/chendeng8899/8487336(“动态加载javaagent运行时”部分)。 - pierpytom
我正在使用jdk 1.8。我尝试过oracle和openjdk,但我从未能够附加到jvm。该尝试会导致目标jvm中的线程转储,并在注入jvm时失败,显示com.sun.tools.attach.AttachNotSupportedException:无法打开套接字文件:目标进程未响应或HotSpot VM未加载于sun.tools.attach.LinuxVirtualMachine.<init>(LinuxVirtualMachine.java:106) at sun.tools.attach.LinuxAttachProvider.attachVirtualMachine(LinuxAttachProvider.java:63) at com.sun.tools.attach.VirtualMachine.attach(VirtualMachine.java:208) at Main.main(Main.java:15) - Sam Thomas
我已经在CentOS和Ubuntu上尝试过了。两者都以同样的方式失败了。 - Sam Thomas
它需要tools.jar,是否值得将tools.jar添加到类路径中。(使用Java8) - Ashok Waghmare

4
你应该能够在Java 6中完成它,参见“VM启动后启动代理”章节的包文档。编辑:也许在Java 5中已经可以实现了,只是Javadoc没有那么明确地提到它。

它没有指定方法调用是什么。然而,进一步研究会发现((URLClassLoader)ClassLoader.getSystemClassLoader()).addURL(....),其中添加的URL指向myagent.jar,这将导致调用agentmain? - Paul Keeble
这是一般的Java 6还是只有HotSpot? - Thorbjørn Ravn Andersen
@Paul:我没有尝试过,所以不能确定它是否像那样工作,但这似乎是合理的。但是,由于它是受保护的,因此您将不得不通过反射调用addURL。类似这样: URLClassLoader sysloader = (URLClassLoader)ClassLoader.getSystemClassLoader(); Class sysclass = URLClassLoader.class; try { Method method = sysclass.getDeclaredMethod("addURL",parameters); method.setAccessible(true); method.invoke(sysloader,new Object[]{ yourURL }); } - HerdplattenToni

1

遇到相同的问题后,我找到了一个更全面的解决方案,来自ByteBuddy库

ByteBuddy彻底尝试动态加载其Java代理:

安装代理程序到当前运行的Java虚拟机。不幸的是,这并不总是有效。Java代理程序的运行时安装支持以下情况:
  • JVM版本9+:对于至少版本9的Java VM,附件API已移动到一个模块中,如果{@code jdk.attach}模块对Byte Buddy可用,则可以进行运行时安装,通常仅适用于使用JDK提供的VM。
  • OpenJDK / Oracle JDK / IBM J9版本8-:HotSpot的安装仅在与JDK捆绑在一起时才可能,并且需要与VM捆绑的{@code tools.jar},通常仅适用于JVM的JDK版本。
  • 在Linux上运行并包含可选的junixsocket-native-common依赖项时,Byte Buddy会模拟Unix套接字连接以附加到目标VM。

  • 网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接