什么是Instrumentation?

java Instrumentation指的是可以用独立于应用程序之外的代理(agent)程序来监测和协助运行在JVM上的应用程序。这种监测和协助包括但不限于获取JVM运行时状态,替换和修改类定义等。 Java SE5中使用JVM TI替代了JVM PI和JVM DI。提供一套代理机制,支持独立于JVM应用程序之外的程序以代理的方式连接和访问JVM。java.lang.instrument是在JVM TI的基础上提供的Java版本的实现。 Instrumentation提供的主要功能是修改jvm中类的行为。 Java SE6中由两种应用Instrumentation的方式,premain(命令行)和agentmain(运行时)

premain方式

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

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

在这个jar包的manifest文件中包含Premain-Class属性,并且改属性的值为代理类全路径名。
代理类必须提供一个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方法,代码如下:

package test;import java.lang.instrument.Instrumentation;public class Agent {public static void premain(String args, Instrumentation inst){System.out.println("Hi, I'm agent!");inst.addTransformer(new TestTransformer());}
}

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

inst.addTransformer(new TestTransformer());
这行代码的意思是向inst中添加一个类的转换器。用于转换类的行为。

构造Transformer

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

package test;import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.security.ProtectionDomain;import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.FieldInsnNode;
import org.objectweb.asm.tree.InsnList;
import org.objectweb.asm.tree.LdcInsnNode;
import org.objectweb.asm.tree.MethodInsnNode;
import org.objectweb.asm.tree.MethodNode;public class TestTransformer implements ClassFileTransformer {@Overridepublic byte[] transform(ClassLoader arg0, String arg1, Class<?> arg2,ProtectionDomain arg3, byte[] arg4)throws IllegalClassFormatException {ClassReader cr = new ClassReader(arg4);ClassNode cn = new ClassNode();cr.accept(cn, 0);for (Object obj : cn.methods) {MethodNode md = (MethodNode) obj;if ("<init>".endsWith(md.name) || "<clinit>".equals(md.name)) {continue;}InsnList insns = md.instructions;InsnList il = new InsnList();il.add(new FieldInsnNode(Opcodes.GETSTATIC, "java/lang/System","out", "Ljava/io/PrintStream;"));il.add(new LdcInsnNode("Enter method-> " + cn.name+"."+md.name));il.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL,"java/io/PrintStream", "println", "(Ljava/lang/String;)V"));insns.insert(il);md.maxStack += 3;}ClassWriter cw = new ClassWriter(0);cn.accept(cw);return cw.toByteArray();}}

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

设置MANIFEST.MF

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

Manifest-Version: 1.0
Premain-Class: test.Agent
Created-By: 1.6.0_29

测试

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

public class TestAgent {public static void main(String[] args) {TestAgent ta = new TestAgent();ta.test();}public void test() {System.out.println("I'm TestAgent");}}

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

java -javaagent:agent.jar TestAgent

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

Hi, I’m agent!
Enter method-> test/TestAgent.main
Enter method-> test/TestAgent.test
I’m TestAgent
Enter method-> java/util/IdentityHashMapKeySet.iteratorEntermethod−>java/util/IdentityHashMapKeySet.iterator Enter method-> java/util/IdentityHashMapKeySet.iteratorEntermethod−>java/util/IdentityHashMapIdentityHashMapIterator.hasNext
Enter method-> java/util/IdentityHashMapKeyIterator.nextEntermethod−>java/util/IdentityHashMapKeyIterator.next Enter method-> java/util/IdentityHashMapKeyIterator.nextEntermethod−>java/util/IdentityHashMapIdentityHashMapIterator.nextIndex
Enter method-> java/util/IdentityHashMapIdentityHashMapIterator.hasNextEntermethod−>java/util/IdentityHashMapIdentityHashMapIterator.hasNext Enter method-> java/util/IdentityHashMapIdentityHashMapIterator.hasNextEntermethod−>java/util/IdentityHashMapKeySet.iterator
Enter method-> java/util/IdentityHashMapIdentityHashMapIterator.hasNextEntermethod−>java/util/IdentityHashMapIdentityHashMapIterator.hasNext Enter method-> java/util/IdentityHashMapIdentityHashMapIterator.hasNextEntermethod−>java/util/IdentityHashMapKeyIterator.next
Enter method-> java/util/IdentityHashMap$IdentityHashMapIterator.nextIndex
Enter method-> com/apple/java/Usage$3.run
。。。
从输出中可以看出,程序首先执行的是代理类中的premain方法(不过代理类自身不会被自己转换,所以不能打印出代理类的方法名),然后是应用程序中的main方法。

agentmain方式

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

在manifest中指定Agent-Class属性,值为代理类全路径
代理类需要提供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。

JAVA Instrumentation相关推荐

  1. 用Java Instrumentation 在类加载时添加记录

    用Java Instrumentation 在类加载时添加记录 发布者:xanadu0214   来源:网络转载   发布日期:2013年11月06日   Java学习交流群:471651004 在分 ...

  2. GraphQL Java - Instrumentation

    Instrumentation拦截器 通过实现Instrumentation接口,可以观察一个query的执行,或修改运行期的行为. 最常见的用途是进行性能监控,和自定义日志记录,但它也可以用于完成其 ...

  3. 作为一名Java程序员,你竟然不知道Instrumentation

    转载自  作为一名Java程序员,你竟然不知道Instrumentation 作者:Yilun Fan  来源:http://1t.click/fMA 对于Java 程序员来说,Java Instru ...

  4. java.lang.instrument 学习(一)

    [+] 转自:http://jiangbo.me/blog/2012/02/21/Java-lang-instrument/ Instrumentation介绍: Java Instrumentati ...

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

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

  6. 说实话,你工作5年,不知道什么是Java agent技术,让我很吃惊...

    注:本文定义-在函数执行前后增加对应的逻辑的操作统称为MOCK. 引子 在某天与QA同学进行沟通时,发现QA同学有针对某个方法调用时,有让该方法停止一段时间的需求,我对这部分的功能实现非常好奇,因此决 ...

  7. java agent技术原理及简单实现

    注:本文定义-在函数执行前后增加对应的逻辑的操作统称为MOCK 1.引子 在某天与QA同学进行沟通时,发现QA同学有针对某个方法调用时,有让该方法停止一段时间的需求,我对这部分的功能实现非常好奇,因此 ...

  8. 在运行时修补Java

    本文将重点介绍如何解决与第三方库相关的问题 不能被规避 难以排除/绕过/替换 只需不提供错误修正 在这种情况下,解决问题仍然是一项艰巨的任务. 作为这种情况的诱因,请考虑对"哈希索引&quo ...

  9. dll 源码_【技术分享】 | 一个JAVA内存马的源码分析

    前言 偶然接触到了这样一个JAVA内存马,其作者也是冰蝎的作者,项目地址: https://github.com/rebeyond/memShell 正好最近在接触JAVA,借此机会学习下大佬的代码, ...

最新文章

  1. AAAI2021论文:一个激光雷达点云的3D目标单步检测法CIA-SSD
  2. 布线技术不断演进满足快速增长的网络需求
  3. php 168任意代码执行漏洞之php的Complex (curly) syntax
  4. php 以-截取剩余的字符串_10分钟从PHP到Python
  5. Ionic系列——环境配置和项目搭建
  6. customplot设置单个点的颜色_CAD教程,CAD大神总结CAD快捷键及一些参数设置大集合,码走...
  7. 硬盘检测工具Smartmontools安装、部署、使用
  8. jsbridge实现及原理_JSBridge 实现原理解析
  9. jmeter 插件 监视器 图形界面使用
  10. centos6.8 安装php7
  11. ansible-playbook Roles include
  12. weex android 滑动事件,【报Bug】weex编译模式下slider组件 @scroll 事件, 滑块左右滑动, @scroll 回调的值始终是负数, 判断不了左右动作...
  13. 实战项目 仿写小米商城 网页框架
  14. centos8干净卸载mysql
  15. keytool命令详解 自签名证书
  16. 3天72小时,全国首个区块链周都讲了些啥 | 一文读懂
  17. 微信小程序里面嵌套的h5使用微信sdk配置踩坑
  18. css动画小案例(太阳地球月球运动轨迹)
  19. 永远不怕IE主页地址被修改
  20. 使用python3查收与发送邮件

热门文章

  1. jvm 崩溃日志设置_JVM崩溃的原因及解决过程
  2. 几个很“高雅”的测试
  3. 【Vue】Vue基础总结 思维导图
  4. 网易游戏探索人脸识别技术,未成年游戏防沉迷监管日趋完善
  5. TP5与TP6的差异
  6. 18 原型模式(原型设计模式)详解
  7. Storm工作原理集群环境搭建
  8. 没有注册类 (Exception from HRESULT: 0x80040154 (REGDB_E_CLASSNOTREG))
  9. ubuntu中微信突然打不开了
  10. 转载一篇文章-这七句话被中国人误传了数千年