Instrumentation(1)
Instrumentation介绍:
JavaInstrumentation指的是可以用独立于应用程序之外的代理(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 -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 {
- @Override
- public 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/IdentityHashMap$KeySet.iterator
- Enter method-> java/util/IdentityHashMap$IdentityHashMapIterator.hasNext
- Enter method-> java/util/IdentityHashMap$KeyIterator.next
- Enter method-> java/util/IdentityHashMap$IdentityHashMapIterator.nextIndex
- Enter method-> java/util/IdentityHashMap$IdentityHashMapIterator.hasNext
- Enter method-> java/util/IdentityHashMap$KeySet.iterator
- Enter method-> java/util/IdentityHashMap$IdentityHashMapIterator.hasNext
- Enter method-> java/util/IdentityHashMap$KeyIterator.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。:) 运行该程序,获得进程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
。。。
参考文档
附: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
Instrumentation(1)的更多相关文章
- Instrumentation(3)
摘要: Instrumentation 类加载过程 Instrumentation与Transformer Instrumentation与Transformer的关系 Instrumenta ...
- Android单元测试初探——Instrumentation(转载)
学习Android有一段时间了,虽然前段时间对软件测试有了一些了解,不过接触android的单元测试却是头一次.这几天在物流大赛上也用了不少时间,所以对于android的单元测试没有太深入的研究,所以 ...
- android测试之——Instrumentation(一)
以下是本人原创,如若转载和使用请注明转载地址.本博客信息切勿用于商业,可以个人使用,若喜欢我的博客,请关注我,谢谢!博客地址 感谢您支持我的博客,我的动力是您的支持和关注!如若转载和使用请注明转载地址 ...
- Spring学习之Jar包功能介绍(转)
spring.jar 是包含有完整发布模块的单个jar 包.但是不包括mock.jar, aspects.jar, spring-portlet.jar, and spring-hibernate2. ...
- spring jar包解读(转)
作者:http://www.cnblogs.com/leehongee/archive/2012/10/01/2709541.html spring.jar 是包含有完整发布模块的单个jar 包.但是 ...
- Instrumentation 功能介绍(javaagent)
利用 Java 代码,即 java.lang.instrument 做动态 Instrumentation 是 Java SE 5 的新特性,它把 Java 的 instrument 功能从本地代码中 ...
- 曹工说Spring Boot源码(14)-- AspectJ的Load-Time-Weaving的两种实现方式细细讲解,以及怎么和Spring Instrumentation集成
写在前面的话 相关背景及资源: 曹工说Spring Boot源码(1)-- Bean Definition到底是什么,附spring思维导图分享 曹工说Spring Boot源码(2)-- Bean ...
- 曹工说Spring Boot源码(25)-- Spring注解扫描的瑞士军刀,ASM + Java Instrumentation,顺便提提Jar包破解
写在前面的话 相关背景及资源: 曹工说Spring Boot源码(1)-- Bean Definition到底是什么,附spring思维导图分享 曹工说Spring Boot源码(2)-- Bean ...
- Spring4读书笔记(1)-模块
Srping主要模块 Core Container spring-core,spring-beans: 提供基础功能,包括IoC和DI等特性.对依赖起到解耦作用(BeanFactory). sprin ...
随机推荐
- flask开发过程中的常见问题
1. 使用supervisorctl时报"http://localhost:9001 refused connection"错误 解决方法:使用supervisorctl时指定配置 ...
- Microsoft C++ 异常: std::system_error std::thread
第一次使用std::thread,把之前项目里面的Windows的thread进行了替换,程序退出的然后发生了std::system_error. 经过调试,发现std::thread ,join了两 ...
- 移动App开发基本技术面
1.UI布局 1.1.熟悉系统布局基本机制和使用方法 2.界面效果 2.1.熟悉系统提供的所有界面组件 2.2.熟悉各种功能界面效果的实现途径 2,3.动画等特殊UI效果的实现机制 3.网络请求 3. ...
- FFmpeg and x264 Encoding Guide
https://trac.ffmpeg.org/wiki/Encode/H.264 FFmpeg and H.264 Encoding Guide Contents Constant Rate Fac ...
- txtbook简单HTML可读化改造
一般来讲下载的txtbook在notepad或者之类的文本编辑器下的阅读体验不是很好,PC上面专门装个txt阅读器好像有点杀鸡用牛刀,可以用HTML简单处理一下就可以放在浏览器下爽快的阅读了,这个操作 ...
- nvm使用笔记
1.先发个中文博客的链接:http://www.cnblogs.com/kaiye/p/4937191.html 2.安装node版本的命令问题,版本号前面要加v,安装6.9.1的正确命令是: nvm ...
- Mac下的Bash配置文件冲突问题
Mac下默认的Bash配置文件是~/.profile.有的软件安装时会生成~/.bash_profiel.有了这个文件.之前的.profiel就不会再被加载,需要手动把里面的文件内容转移到.bash_ ...
- iview源码解析(1)
概述 公司技术栈开始用vue主导开发,但因为公司前端会vue的不多所以在项目中用到vue的技术不是很深,之前出去面试被接连打击,而且本来打算开始为公司vue的项目构建自己的组件库所以去下载了iview ...
- ExtJs radiogroup form.loadRecord方法无法赋值正确解决办法
一.radiogroup的name和radio的name一致,inputValue为整形 { xtype: 'radiogroup', fieldLabel: '是否有效', name: 'statu ...
- vim快捷键汇总
命令历史 以:和/开头的命令都有历史纪录,可以首先键入:或/然后按上下箭头来选择某个历史命令. 启动vim 在命令行窗口中输入以下命令即可 vim 直接启动vim vim filename 打开vim ...