Java 探针:Java SE 6 提供的 Attach API
2016-06-28 11:41
549 查看
http://www.ibm.com/developerworks/cn/java/j-lo-jse61/index.html
在 Java SE 5 的基础上,Java SE 6 针对这种状况做出了改进,开发者可以在 main 函数开始执行以后,再启动自己的 Instrumentation 程序。
在 Java SE 6 的 Instrumentation 当中,有一个跟 premain“并驾齐驱”的“agentmain”方法,可以在 main 函数开始运行之后再运行。
跟 premain 函数一样, 开发者可以编写一个含有“agentmain”函数的 Java 类:
public static void agentmain (String agentArgs, Instrumentation inst); [1]
public static void agentmain (String agentArgs); [2]
同样,[1] 的优先级比 [2] 高,将会被优先执行。
与“Premain-Class”类似,开发者必须在 manifest 文件里面设置“Agent-Class”来指定包含 agentmain 函数的类。
可是,跟 premain 不同的是,agentmain 需要在 main 函数开始运行后才启动,这样的时机应该如何确定呢,这样的功能又如何实现呢?
在 Java SE 6 文档当中,开发者也许无法在 java.lang.instrument 包相关的文档部分看到明确的介绍,更加无法看到具体的应用 agnetmain 的例子。不过,在 Java SE 6 的新特性里面,有一个不太起眼的地方,揭示了 agentmain 的用法。这就是 Java SE 6 当中提供的 Attach API。
Attach API 不是 Java 的标准 API,而是 Sun 公司提供的一套扩展 API,用来向目标 JVM ”附着”(Attach)代理工具程序的。有了它,开发者可以方便的监控一个 JVM,运行一个外加的代理程序。
Attach API 很简单,只有 2 个主要的类,都在 com.sun.tools.attach 包里面: VirtualMachine 代表一个 Java 虚拟机,也就是程序需要监控的目标虚拟机,提供了 JVM 枚举,Attach 动作和 Detach 动作(Attach 动作的相反行为,从 JVM 上面解除一个代理)等等 ; VirtualMachineDescriptor 则是一个描述虚拟机的容器类,配合 VirtualMachine 类完成各种功能。
使用业务类:
PS:如果业务类发生变化,使用者打印会感知到。
下面是 instrument 相关的类:
动态Agent:
业务转换类:
业务转换类会从 /home/conquer/Desktop/aaaa/TransClass.class 这个位置加载一个新的类以替换原有的业务类,注意这个类必需和原有业务类全名称相同,测试过程可以将返回值改成2或其它,以区分发生了变化,让业务使用类打印出一个新值。
将上面两个类达成jar包,如 agentmain.jar,并添加:META-INF/MANIFEST.MF,内容:
好了,开始测试,先运行TestMainInJar循环打印当前的业务方法返回值,然后运行测试类:
这样就能看到打印结果在运行agent之后由0变成2,类字节码被成功替换了。
以上,我们将TransClass定义为一个全局变量,循环调用打印一个旧实例的方法返回值,同样可以看到class替换后的结果。(PS:如果对jvm内部理解透彻的话是很好理解的,实例在内存中只是保存了区别于其它实例的属性或成员变量,其方法的执行序列依然走的是class的定义)
概述
在 Java SE 5 当中,开发者只能在 premain 当中施展想象力,所作的 Instrumentation 也仅限与 main 函数执行前,这样的方式存在一定的局限性。在 Java SE 5 的基础上,Java SE 6 针对这种状况做出了改进,开发者可以在 main 函数开始执行以后,再启动自己的 Instrumentation 程序。
在 Java SE 6 的 Instrumentation 当中,有一个跟 premain“并驾齐驱”的“agentmain”方法,可以在 main 函数开始运行之后再运行。
跟 premain 函数一样, 开发者可以编写一个含有“agentmain”函数的 Java 类:
public static void agentmain (String agentArgs, Instrumentation inst); [1]
public static void agentmain (String agentArgs); [2]
同样,[1] 的优先级比 [2] 高,将会被优先执行。
与“Premain-Class”类似,开发者必须在 manifest 文件里面设置“Agent-Class”来指定包含 agentmain 函数的类。
可是,跟 premain 不同的是,agentmain 需要在 main 函数开始运行后才启动,这样的时机应该如何确定呢,这样的功能又如何实现呢?
在 Java SE 6 文档当中,开发者也许无法在 java.lang.instrument 包相关的文档部分看到明确的介绍,更加无法看到具体的应用 agnetmain 的例子。不过,在 Java SE 6 的新特性里面,有一个不太起眼的地方,揭示了 agentmain 的用法。这就是 Java SE 6 当中提供的 Attach API。
Attach API 不是 Java 的标准 API,而是 Sun 公司提供的一套扩展 API,用来向目标 JVM ”附着”(Attach)代理工具程序的。有了它,开发者可以方便的监控一个 JVM,运行一个外加的代理程序。
Attach API 很简单,只有 2 个主要的类,都在 com.sun.tools.attach 包里面: VirtualMachine 代表一个 Java 虚拟机,也就是程序需要监控的目标虚拟机,提供了 JVM 枚举,Attach 动作和 Detach 动作(Attach 动作的相反行为,从 JVM 上面解除一个代理)等等 ; VirtualMachineDescriptor 则是一个描述虚拟机的容器类,配合 VirtualMachine 类完成各种功能。
实例
业务类:package agentmain; public class TransClass { public int getNumber() { return 0; } }
使用业务类:
package agentmain; public class TestMainInJar { public static void main(String[] args) throws InterruptedException { System.out.println(new TransClass().getNumber()); while (true) { Thread.sleep(2000); int number = new TransClass().getNumber(); System.out.println(number); } } }
PS:如果业务类发生变化,使用者打印会感知到。
下面是 instrument 相关的类:
动态Agent:
package agentmain; import java.lang.instrument.Instrumentation; import java.lang.instrument.UnmodifiableClassException; public class AgentMain { public static void agentmain(String agentArgs, Instrumentation inst) throws ClassNotFoundException, UnmodifiableClassException, InterruptedException { System.out.println("Agent Main Done"); inst.addTransformer(new Transformer(), true); inst.retransformClasses(TransClass.class); } }
业务转换类:
package agentmain; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.lang.instrument.ClassFileTransformer; import java.lang.instrument.IllegalClassFormatException; import java.security.ProtectionDomain; public class Transformer implements ClassFileTransformer { public static final String classNumberReturns2 = "/home/conquer/Desktop/aaaa/TransClass.class"; public static byte[] getBytesFromFile(String fileName) { try { System.out.println(System.getProperty("user.home")); // precondition File file = new File(fileName); InputStream is = new FileInputStream(file); long length = file.length(); byte[] bytes = new byte[(int) length]; // Read in the bytes int offset = 0; int numRead = 0; while (offset < bytes.length && (numRead = is.read(bytes, offset, bytes.length - offset)) >= 0) { offset += numRead; } if (offset < bytes.length) { throw new IOException("Could not completely read file " + file.getName()); } is.close(); return bytes; } catch (Exception e) { System.out.println("error occurs in _ClassTransformer!" + e.getClass().getName()); return null; } } public byte[] transform(ClassLoader l, String className, Class<?> c, ProtectionDomain pd, byte[] b) throws IllegalClassFormatException { System.out.println(className); if (!className.contains("TransClass")) { System.out.println("no"); return null; } else { System.out.println("yes"); } return getBytesFromFile(classNumberReturns2); } }
业务转换类会从 /home/conquer/Desktop/aaaa/TransClass.class 这个位置加载一个新的类以替换原有的业务类,注意这个类必需和原有业务类全名称相同,测试过程可以将返回值改成2或其它,以区分发生了变化,让业务使用类打印出一个新值。
将上面两个类达成jar包,如 agentmain.jar,并添加:META-INF/MANIFEST.MF,内容:
Manifest-Version: 1.0 Agent-Class: agentmain.AgentMain
好了,开始测试,先运行TestMainInJar循环打印当前的业务方法返回值,然后运行测试类:
package agentmain; import com.sun.tools.attach.VirtualMachine; import com.sun.tools.attach.VirtualMachineDescriptor; import java.util.List; public class AttachTest extends Thread { private final List<VirtualMachineDescriptor> listBefore; private final String jar; AttachTest(String attachJar, List<VirtualMachineDescriptor> vms) { listBefore = vms; // 记录程序启动时的 VM 集合 jar = attachJar; } public void run() { VirtualMachine vm = null; List<VirtualMachineDescriptor> listAfter = null; try { int count = 0; while (true) { listAfter = VirtualMachine.list(); for (VirtualMachineDescriptor vmd : listAfter) { if (!listBefore.contains(vmd)) { // 如果 VM 有增加,我们就认为是被监控的 VM 启动了 // 这时,我们开始监控这个 VM vm = VirtualMachine.attach(vmd); break; } } Thread.sleep(500); count++; if (null != vm || count >= 10) { break; } } vm.loadAgent(jar); // vm.detach(); } catch (Exception e) { e.printStackTrace(); } } public static void main(String[] args) throws Exception { // new AttachThread("TestInstrument1.jar", VirtualMachine.list()).run(); // VirtualMachine attach = VirtualMachine.attach("5741"); // System.out.println(attach.id()); for (VirtualMachineDescriptor vmd : VirtualMachine.list()) { System.out.println(vmd); if (vmd.displayName().contains("TestMainInJar")) { VirtualMachine vm = VirtualMachine.attach(vmd); vm.loadAgent("/home/conquer/Desktop/aaaa/agentmain.jar"); System.out.println("loaded"); vm.detach(); System.out.println("detached"); break; } } } }
这样就能看到打印结果在运行agent之后由0变成2,类字节码被成功替换了。
最后说明1
测试使用用例的时候我们是通过每次new一个新的对象来观察修改结果,其实不是必需new的,因为方法的执行序列是存储在方法区(class文件的定义区),无论使用新的实例还是旧的(用旧的class创建的)实例都是可以感知到class的变化的,如,测试用例可以修改为:package agentmain; public class TestMainInJar { static TransClass transClass = new TransClass(); public static void main(String[] args) throws InterruptedException { while (true) { Thread.sleep(2000); System.out.println(transClass + " ==: " + transClass.getNumber()); } } }
以上,我们将TransClass定义为一个全局变量,循环调用打印一个旧实例的方法返回值,同样可以看到class替换后的结果。(PS:如果对jvm内部理解透彻的话是很好理解的,实例在内存中只是保存了区别于其它实例的属性或成员变量,其方法的执行序列依然走的是class的定义)
最后说明2
相对于jdk5只能通过启动脚本添加javaagent的方式植入代理,jdk6的动态attach也只是免去了修改启动脚本和不用重启的工作,并没有添加其它新的特性,即使不使用动态attach而是使用脚本添加javaagent的方式也可以达到随时修改class定义的目的,无论通过什么方式我们只要获取了 Instrumentation 实例,然后调用其addTransformer方法添加类转换器再调用retransformClasses就可以转换一个类的字节序列了,这里需要注意的是retransformClasses是jdk1.6定义的,jdk1.5只能使用redefineClasses,retransformClasses功能强大使用简单,但有不能修改方法签名,只能修改body等约束,redefineClasses则是一个可定细节制化的选择。最后说明3
大名鼎鼎的Btrace就是基于jdk6实现的,它使用到了jdk6的动态attach(非脚本模式),同时使用到了jdk6提供的retransformClasses,其实要方便地操作字节码最好还是基于JDK6做吧。相关文章推荐
- MyBatis入门第2天--MyBatis基础知识(二)
- java.text.Format及相关类详解
- java中的类修饰符、成员变量修饰符、方法修饰符
- Spring Data JPA 快速入门
- 64位win7环境eclipse集成svn后出现Failed to load JavaHL Library的解决办法
- eclipse *.vm 文件,语法高亮
- html传到后台的中文乱码(后台为java)
- java虚拟机收集器之老年代收集器
- leetcode-java-137. Single Number II
- 取消eclipse js验证
- 项目从eclipse迁移到as
- eclipse birt报表
- java的4中对象引用方式
- Java程序打包成exe
- Spring4.x官方参考文档中文版——第21章 Web MVC框架(16)
- Java泛型及泛型通配符
- leetcode-java-268. Missing Number
- Android Design Support Library在eclipse中使用
- spring websocket + stomp 实现广播通信和一对一通信<转>
- Java的Integer与int互转