[+]

转自:http://jiangbo.me/blog/2012/02/21/Java-lang-instrument/

Instrumentation介绍:

Java Instrumentation指的是可以用独立于应用程序之外的代理(agent)程序来监测和协助运行在JVM上的应用程序。这种监测和协助包括但不限于获取JVM运行时状态,替换和修改类定义等。 Java SE5中使用JVM TI替代了JVM PI和JVM DI。提供一套代理机制,支持独立于JVM应用程序之外的程序以代理的方式连接和访问JVM。Instrumentation 的最大作用就是类定义的动态改变和操作。在 Java SE 5 及其后续版本当中,开发者可以在一个普通 Java 程序(带有 main 函数的 Java 类)运行时,通过 – javaagent 参数指定一个特定的 jar 文件(包含 Instrumentation 代理)来启动 Instrumentation 的代理程序。

premain方式
在Java SE5时代,Instrument只提供了premain一种方式,即在真正的应用程序(包含main方法的程序)main方法启动前启动一个代理程序。例如使用如下命令:

[java] view plaincopy
  1. java -javaagent:agent_jar_path[=options] java_app_name

可以在启动名为java_app_name的应用之前启动一个agent_jar_path指定位置的agent jar。 实现这样一个agent jar包,必须满足两个条件:

  1. 在这个jar包的manifest文件中包含Premain-Class属性,并且改属性的值为代理类全路径名。
  2. 代理类必须提供一个public static void premain(String args, Instrumentation inst)或 public static void premain(String args) 方法。

当在命令行启动该代理jar时,VM会根据manifest中指定的代理类,使用于main类相同的系统类加载器(即ClassLoader.getSystemClassLoader()获得的加载器)加载代理类。在执行main方法前执行premain()方法。如果premain(String args, Instrumentation inst)和premain(String args)同时存在时,优先使用前者。其中方法参数args即命令中的options,类型为String(注意不是String[]),因此如果需要多个参数,需要在方法中自己处理(比如用";"分割多个参数之类);inst是运行时由VM自动传入的Instrumentation实例,可以用于获取VM信息。

premain实例-打印所有的方法调用
下面实现一个打印程序执行过程中所有方法调用的功能,这个功能可以通过AOP其他方式实现,这里只是尝试使用Instrumentation进行ClassFile的字节码转换实现:

构造agent类

premain方式的agent类必须提供premain方法,代码如下:

[java] view plaincopy
  1. package test;
  2. import java.lang.instrument.Instrumentation;
  3. public class Agent {
  4. public static void premain(String args, Instrumentation inst){
  5. System.out.println("Hi, I'm agent!");
  6. inst.addTransformer(new TestTransformer());
  7. }
  8. }

premain有两个参数,args为自定义传入的代理类参数,inst为VM自动传入的Instrumentation实例。 premain方法的内容很简单,除了标准输出外,只有

[java] view plaincopy
  1. inst.addTransformer(new TestTransformer());

这行代码的意思是向inst中添加一个类的转换器。用于转换类的行为。

构造Transformer

下面来实现上述过程中的TestTransformer来完成打印调用方法的类定义转换。

[java] view plaincopy
  1. package test;
  2. import java.lang.instrument.ClassFileTransformer;
  3. import java.lang.instrument.IllegalClassFormatException;
  4. import java.security.ProtectionDomain;
  5. import org.objectweb.asm.ClassReader;
  6. import org.objectweb.asm.ClassWriter;
  7. import org.objectweb.asm.Opcodes;
  8. import org.objectweb.asm.tree.ClassNode;
  9. import org.objectweb.asm.tree.FieldInsnNode;
  10. import org.objectweb.asm.tree.InsnList;
  11. import org.objectweb.asm.tree.LdcInsnNode;
  12. import org.objectweb.asm.tree.MethodInsnNode;
  13. import org.objectweb.asm.tree.MethodNode;
  14. public class TestTransformer implements ClassFileTransformer {
  15. @Override
  16. public byte[] transform(ClassLoader arg0, String arg1, Class<?> arg2,
  17. ProtectionDomain arg3, byte[] arg4)
  18. throws IllegalClassFormatException {
  19. ClassReader cr = new ClassReader(arg4);
  20. ClassNode cn = new ClassNode();
  21. cr.accept(cn, 0);
  22. for (Object obj : cn.methods) {
  23. MethodNode md = (MethodNode) obj;
  24. if ("<init>".endsWith(md.name) || "<clinit>".equals(md.name)) {
  25. continue;
  26. }
  27. InsnList insns = md.instructions;
  28. InsnList il = new InsnList();
  29. il.add(new FieldInsnNode(Opcodes.GETSTATIC, "java/lang/System",
  30. "out", "Ljava/io/PrintStream;"));
  31. il.add(new LdcInsnNode("Enter method-> " + cn.name+"."+md.name));
  32. il.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL,
  33. "java/io/PrintStream", "println", "(Ljava/lang/String;)V"));
  34. insns.insert(il);
  35. md.maxStack += 3;
  36. }
  37. ClassWriter cw = new ClassWriter(0);
  38. cn.accept(cw);
  39. return cw.toByteArray();
  40. }
  41. }

TestTransformer实现了ClassFileTransformer接口,该接口只有一个transform方法,参数传入包括该类的类加载器,类名,原字节码字节流等,返回被转换后的字节码字节流。 TestTransformer主要使用ASM实现在所有的类定义的方法中,在方法开始出添加了一段打印该类名和方法名的字节码。在转换完成后返回新的字节码字节流。详细的ASM使用请参考ASM手册。

设置MANIFEST.MF

设置MANIFEST.MF文件中的属性,文件内容如下:

[java] view plaincopy
  1. Manifest-Version: 1.0
  2. Premain-Class: test.Agent
  3. Created-By: 1.6.0_29

测试
代码编写完成后将代码编译打成agent.jar。 编写测试代码:

[java] view plaincopy
  1. public class TestAgent {
  2. public static void main(String[] args) {
  3. TestAgent ta = new TestAgent();
  4. ta.test();
  5. }
  6. public void test() {
  7. System.out.println("I'm TestAgent");
  8. }
  9. }

从命令行执行该类,并设置agent.jar

[java] view plaincopy
  1. java -javaagent:agent.jar TestAgent

将打印出程序运行过程中实际执行过的所有方法名:

[java] view plaincopy
  1. Hi, I'm agent!
  2. Enter method-> test/TestAgent.main
  3. Enter method-> test/TestAgent.test
  4. I'm TestAgent
  5. Enter method-> java/util/IdentityHashMap$KeySet.iterator
  6. Enter method-> java/util/IdentityHashMap$IdentityHashMapIterator.hasNext
  7. Enter method-> java/util/IdentityHashMap$KeyIterator.next
  8. Enter method-> java/util/IdentityHashMap$IdentityHashMapIterator.nextIndex
  9. Enter method-> java/util/IdentityHashMap$IdentityHashMapIterator.hasNext
  10. Enter method-> java/util/IdentityHashMap$KeySet.iterator
  11. Enter method-> java/util/IdentityHashMap$IdentityHashMapIterator.hasNext
  12. Enter method-> java/util/IdentityHashMap$KeyIterator.next
  13. Enter method-> java/util/IdentityHashMap$IdentityHashMapIterator.nextIndex
  14. Enter method-> com/apple/java/Usage$3.run
  15. 。。。

从输出中可以看出,程序首先执行的是代理类中的premain方法(不过代理类自身不会被自己转换,所以不能打印出代理类的方法名),然后是应用程序中的main方法。

agentmain方式

premain时Java SE5开始就提供的代理方式,给了开发者诸多惊喜,不过也有些须不变,由于其必须在命令行指定代理jar,并且代理类必须在main方法前启动。因此,要求开发者在应用前就必须确认代理的处理逻辑和参数内容等等,在有些场合下,这是比较苦难的。比如正常的生产环境下,一般不会开启代理功能,但是在发生问题时,我们不希望停止应用就能够动态的去修改一些类的行为,以帮助排查问题,这在应用启动前是无法确定的。 为解决运行时启动代理类的问题,Java SE6开始,提供了在应用程序的VM启动后在动态添加代理的方式,即agentmain方式。 与Permain类似,agent方式同样需要提供一个agent jar,并且这个jar需要满足:

  1. 在manifest中指定Agent-Class属性,值为代理类全路径
  2. 代理类需要提供public static void agentmain(String args, Instrumentation inst)或public static void agentmain(String args)方法。并且再二者同时存在时以前者优先。args和inst和premain中的一致。

不过如此设计的再运行时进行代理有个问题——如何在应用程序启动之后再开启代理程序呢? JDK6中提供了Java Tools API,其中Attach API可以满足这个需求。

Attach API中的VirtualMachine代表一个运行中的VM。其提供了loadAgent()方法,可以在运行时动态加载一个代理jar。具体需要参考《Attach API》

agentmain实例-打印当前已加载的类

构造agent类

agentmain方式的代理类必须提供agentmain方法:

package loaded;import java.lang.instrument.Instrumentation;public class LoadedAgent {@SuppressWarnings("rawtypes")public static void agentmain(String args, Instrumentation inst){Class[] classes = inst.getAllLoadedClasses();for(Class cls :classes){System.out.println(cls.getName());}}
}

agentmain方法通过传入的Instrumentation实例获取当前系统中已加载的类。

设置MANNIFEST.MF

设置MANIFEST.MF文件,指定Agent-Class:

Manifest-Version: 1.0
Agent-Class: loaded.LoadedAgent
Created-By: 1.6.0_29

绑定到目标VM

将agent类和MANIFEST.MF文件编译打成loadagent.jar后,由于agent main方式无法向pre main方式那样在命令行指定代理jar,因此需要借助Attach Tools API。

package attach;import java.io.IOException;import com.sun.tools.attach.AgentInitializationException;
import com.sun.tools.attach.AgentLoadException;
import com.sun.tools.attach.AttachNotSupportedException;
import com.sun.tools.attach.VirtualMachine;public class Test {public static void main(String[] args) throws AttachNotSupportedException,IOException, AgentLoadException, AgentInitializationException {VirtualMachine vm = VirtualMachine.attach(args[0]);vm.loadAgent("/Users/jiangbo/Workspace/code/java/javaagent/loadagent.jar");}}

该程序接受一个参数为目标应用程序的进程id,通过Attach Tools API的VirtualMachine.attach方法绑定到目标VM,并向其中加载代理jar。

构造目标测试程序

构造一个测试用的目标应用程序:

package attach;public class TargetVM {public static void main(String[] args) throws InterruptedException{while(true){Thread.sleep(1000);}}
}

这个测试程序什么都不做,只是不停的sleep。:) 运行该程序,获得进程ID=33902。 运行上面绑定到VM的Test程序,将进程id作为参数传入:

java attach.Test 33902

观察输出,会打印出系统当前所有已经加载类名

java.lang.NoClassDefFoundError
java.lang.StrictMath
java.security.SignatureSpi
java.lang.Runtime
java.util.Hashtable$EmptyEnumerator
sun.security.pkcs.PKCS7
java.lang.InterruptedException
java.io.FileDescriptor$1
java.nio.HeapByteBuffer
java.lang.ThreadGroup
[Ljava.lang.ThreadGroup;
java.io.FileSystem
。。。

参考文档

  • java.lang.instrument API docs
  • The Attach API
  • Java SE6新特性:Instrumentation新功能

附:agent jar中manifest的属性

  • Premain-Class: 当在VM启动时,在命令行中指定代理jar时,必须在manifest中设置Premain-Class属性,值为代理类全类名,并且该代理类必须提供premain方法。否则JVM会异常终止。
  • Agent-Class: 当在VM启动之后,动态添加代理jar包时,代理jar包中manifest必须设置Agent-Class属性,值为代理类全类名,并且该代理类必须提供agentmain方法,否则无法启动该代理。
  • Boot-Class-Path: Bootstrap class loader加载类时的搜索路径,可选。
  • Can-Redefine-Classes: true/false;标示代理类是否能够重定义类。可选。
  • Can-Retransform-Classes: true/false;标示代理类是否能够转换类定义。可选。
  • Can-Set-Native-Prefix::true/false;标示代理类是否需要本地方法前缀,可选。

当一个代理jar包中的manifest文件中既有Premain-Class又有Agent-Class时,如果以命令行方式在VM启动前指定代理jar,则使用Premain-Class;反之如果在VM启动后,动态添加代理jar,则使用Agent-Class


java.lang.instrument 学习(一)相关推荐

  1. [转]java.lang.instrument 学习(一)

    [转]java.lang.instrument 学习(一)  收藏 sunyh 发表于 10个月前 阅读 40 收藏 3 点赞 1 评论 0 转自:http://jiangbo.me/blog/201 ...

  2. java.lang.Instrument 动态修改替换类代码

    java.lang.Instrument 动态修改替换类代码 | java.lang.Instrument包是在JDK5引入的,程序员通过修改方法的字节码实现动态修改类代码. 这通常是在类的main方 ...

  3. java.lang.Instrument 代理Agent使用

    原文出处: 陶邦仁 java.lang.Instrument包是在JDK5引入的,程序员通过修改方法的字节码实现动态修改类代码.这通常是在类的main方法调用之前进行预处理的操作,通过java指定该类 ...

  4. java.lang.instrument.Instrumentation文档翻译

    包:java.lang.instrument Instrumentation接口 public interface Instrumentation 这个类提供了插装(Instrument)Java编程 ...

  5. java.lang.instrument ASSERTION FAILED ***: “error

    java.lang.instrument ASSERTION FAILED ***: "error == JVMTI_ERROR_NONE" at Reentrancy.c lin ...

  6. 云计算学习笔记---异常处理---hadoop问题处理ERROR org.apache.hadoop.hdfs.server.datanode.DataNode: java.lang.NullPoin

    云计算学习笔记---异常处理---hadoop问题处理ERROR org.apache.hadoop.hdfs.server.datanode.DataNode: java.lang.NullPoin ...

  7. Spring/SpringBoot学习中遇到的问题记录(2):java.lang.ClassNotFoundException:org.apache.jsp.WEB_002dINF.views.xx

    最近在学习Spring实战第四版,学习到第5章,搭建SpringMVC时碰到了一个大问题.在这里我记录一下自己遇到的问题及解决办法. 首先,先描述一下自己在IDEA中搭建基于Java配置的Spring ...

  8. Java源码学习笔记之lang包——包装类Integer.class

    前言:仅为学习所发现而记录. JDK 版本:1.8 同样的,在去除所有方法和静态变量之后,以下为核心内容. public final class Integer extends Number impl ...

  9. spring mvc学习(50):java.lang.ClassNotFoundException: org.springframework.web.servlet. DispatcherSe

    今天朋友发了个maven项目给我看,问我为什么启动不了.说实话,一直用Jfinal都快不会用spring了- 还是决定看看. 接收了文件,是maven构建的,打开eclipse,导入maven项目,然 ...

最新文章

  1. xilinx LVDS使用注意事项
  2. 不该被忽视的CoreJava细节(四)
  3. 答TOGAF企业架构的一些问题
  4. 计算机文档我的文档丢失,恢复我的电脑窗口中共享文档与我的文档不见了的方法...
  5. android 8 wifi 不稳定,Android 8.0又背锅?网络兼容问题导致WiFi狂掉线
  6. 修改Google toolbar for firefox的默认语言
  7. MATLAB三维散点图的绘制(scatter3、plot3)
  8. Win11怎么卸载软件?Win11彻底卸载软件教程
  9. 云桌面简介,IDV和VDI,桌面虚拟化对比
  10. vscode生成的.BROWSE.VC.DB文件超大问题
  11. 量化交易 米筐 策略评价指标
  12. 简单的外汇市场赚钱方法
  13. 为什么说C++太复杂?复杂的必要性是为什么?
  14. 《新科学家》:十大最不可思议计算机
  15. 访客登记使用人脸识别有什么好处?
  16. 第四届“图灵杯”NEUQ-ACM程序设计人赛真题重现
  17. 凸透镜成像原理以及镜头选型
  18. python中登录基金账户获取基金数据
  19. 火爆全网的老照片修复技术,今天手把手教你用怎么玩
  20. 区分鼠标左键和右键单击

热门文章

  1. 成都大学的计算机排名,成都除了成都大学,还有2所985、4所211,含金量很高
  2. 图十字链表并求度c语言,利用十字链表存储树结构(便于同时求出某一点的入度与出度)------C语言实现...
  3. http status 404 – 未找到_HTTP状态码
  4. java rxtx 64_java – RXTX无法在Windows 7 64位下列出或查找端口
  5. php遍历父元素,PHP遍历函数将单个数组转换为具有子元素的嵌套数组 – 基于父标识...
  6. 小米note3无线显示电脑连接服务器,小米note3如何连接电脑 小米note3连接电脑没反应怎么办...
  7. 八年级计算机软件,信息技术(南方出版社) 八年级下册教案(共14课)
  8. srs audio sandbox 注册机_Windows10自带 Sandbox软件测试必备技巧
  9. java 重复start,iText中带有“start”属性的有序列表[重复]
  10. csv 逗号数量不一样_敲黑板,表格后缀xls、xlsx、xlsb、xlsm、csv分别是什么意思?...