深入字节码 -- 计算方法执行时间
2016-02-22 16:57
357 查看
什么是字节码
看看一段简单代码。
编译后通过命令(
为什么要学习字节码
能了解技术背后的原理,更容易写出高质量代码;
字节码设计非常优秀,发展十几年只仅仅删除和增加几个指令,学懂之后长期受益高,如果懂字节码再学习
开发框架、监控系统、中间件、语言字节码技术都是必杀技;
字节码框架(
操作字节码框架有很多,具体可以参考博文,下面对比
指的是可以用独立于应用程序之外的代理(
计算方法执行时间方式
直接在代码开始和结束出打印当前时间,相减即可得到;
实现一个动态代理,或者借助
上面两种实现方式都需要修改代码或者配置文件,下面我要介绍方式不仅不需要修改代码,而且效率高;
具体实现方式
题记
上面的代码难免有
顺便打点广告,如果看后对字节码技术感兴趣,欢迎加入我们(oneapm)一起做点有意思事情,可直接联系我;
完整代码请访问github;
下一篇结合
java程序通过
javac编译之后生成文件
.class就是字节码集合,正是有这样一种
中间码(字节码),使得
scala/groovy/clojure等函数语言只用实现一个编译器即可运行在
JVM上。
看看一段简单代码。
public long getExclusiveTime() { long startTime = System.currentTimeMillis(); System.out.printf("exclusive code"); long endTime = System.currentTimeMillis(); return endTime - startTime; } public class com.blueware.agent.StartAgent {
编译后通过命令(
javap -c com.blueware.agent.StartAgent)查看,具体含义请参考oracle
public com.blueware.agent.StartAgent(); Code: 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return public long getExclusiveTime(); Code: 0: invokestatic #2 // Method java/lang/System.currentTimeMillis:()J 3: lstore_1 4: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream; 7: ldc #4 // String exclusive code 9: iconst_0 10: anewarray #5 // class java/lang/Object 13: invokevirtual #6 // Method java/io/PrintStream.printf:(Ljava/lang/String;[Ljava/lang/Object;)Ljava/io/PrintStream; 16: pop 17: invokestatic #2 // Method java/lang/System.currentTimeMillis:()J 20: lstore_3 21: lload_3 22: lload_1 23: lsub 24: lreturn }
为什么要学习字节码
能了解技术背后的原理,更容易写出高质量代码;
字节码设计非常优秀,发展十几年只仅仅删除和增加几个指令,学懂之后长期受益高,如果懂字节码再学习
scala/groovy/clojure会容易很多;
开发框架、监控系统、中间件、语言字节码技术都是必杀技;
字节码框架(
ASM/Javassist)
操作字节码框架有很多,具体可以参考博文,下面对比
ASM/Javassist
选项 | 优点 | 缺点 |
---|---|---|
ASM | 速度快、代码量小、功能强大 | 要写字节码、学习曲线高 |
Javassist | 学习简单,不用写字节码 | 比ASM慢,功能少 |
Java Instrumentation介绍
指的是可以用独立于应用程序之外的代理(
agent)程序,
agent程序通过增强字节码动态修改或者新增类,利用这样特性可以设计出更通用的监控、框架、中间件程序,在
JVM启动参数加
–javaagent:agent_jar_path/agent.jar即可运行(在
JDK5及其后续版本才可以),更多关于
Instrumentation知识请参考博文
计算方法执行时间方式
直接在代码开始和结束出打印当前时间,相减即可得到;
实现一个动态代理,或者借助
Spring/AspectJ等框架;
上面两种实现方式都需要修改代码或者配置文件,下面我要介绍方式不仅不需要修改代码,而且效率高;
具体实现方式
1.StartAgent类必须提供
premain方法,代码如下:
public class StartAgent { //代理程序入口函数 public static void premain(String args, Instrumentation inst) { System.out.println("agent begin"); //添加字节码转换器 inst.addTransformer(new PrintTimeTransformer()); System.out.println("agent end"); } }
2.PrintTimeTransformer实现一个转换器,代码如下:
//字节码转化器类 public class PrintTimeTransformer implements ClassFileTransformer { //实现字节码转化接口,一个小技巧建议实现接口方法时写@Override,方便重构 //loader:定义要转换的类加载器,如果是引导加载器,则为 null(在这个小demo暂时还用不到) //className:完全限定类内部形式的类名称和中定义的接口名称,例如"java.lang.instrument.ClassFileTransformer" //classBeingRedefined:如果是被重定义或重转换触发,则为重定义或重转换的类;如果是类加载,则为 null //protectionDomain:要定义或重定义的类的保护域 //classfileBuffer:类文件格式的输入字节缓冲区(不得修改) //一个格式良好的类文件缓冲区(转换的结果),如果未执行转换,则返回 null。 @Override public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException { //简化测试demo,直接写待修改的类(com/blueware/agent/TestTime) if (className != null && className.equals("com/blueware/agent/TestTime")) { //读取类的字节码流 ClassReader reader = new ClassReader(classfileBuffer); //创建操作字节流值对象,ClassWriter.COMPUTE_MAXS:表示自动计算栈大小 ClassWriter writer = new ClassWriter(reader, ClassWriter.COMPUTE_MAXS); //接受一个ClassVisitor子类进行字节码修改 reader.accept(new TimeClassVisitor(writer, className), 8); //返回修改后的字节码流 return writer.toByteArray(); } return null; } }
3.TimeClassVisitor类访问器,实现字节码修改,代码如下:
//定义扫描待修改class的visitor,visitor就是访问者模式 public class TimeClassVisitor extends ClassVisitor { private String className; public TimeClassVisitor(ClassVisitor cv, String className) { super(Opcodes.ASM5, cv); this.className = className; } //扫描到每个方法都会进入,参数详情下一篇博文详细分析 @Override public MethodVisitor visitMethod(int access, final String name, final String desc, String signature, String[] exceptions) { MethodVisitor mv = cv.visitMethod(access, name, desc, signature, exceptions); final String key = className + name + desc; //过来待修改类的构造函数 if (!name.equals("<init>") && mv != null) { mv = new AdviceAdapter(Opcodes.ASM5, mv, access, name, desc) { //方法进入时获取开始时间 @Override public void onMethodEnter() { //相当于com.blueware.agent.TimeUtil.setStartTime("key"); this.visitLdcInsn(key); this.visitMethodInsn(Opcodes.INVOKESTATIC, "com/blueware/agent/TimeUtil", "setStartTime", "(Ljava/lang/String;)V", false); } //方法退出时获取结束时间并计算执行时间 @Override public void onMethodExit(int opcode) { //相当于com.blueware.agent.TimeUtil.setEndTime("key"); this.visitLdcInsn(key); this.visitMethodInsn(Opcodes.INVOKESTATIC, "com/blueware/agent/TimeUtil", "setEndTime", "(Ljava/lang/String;)V", false); //向栈中压入类名称 this.visitLdcInsn(className); //向栈中压入方法名 this.visitLdcInsn(name); //向栈中压入方法描述 this.visitLdcInsn(desc); //相当于com.blueware.agent.TimeUtil.getExclusiveTime("com/blueware/agent/TestTime","testTime"); this.visitMethodInsn(Opcodes.INVOKESTATIC, "com/blueware/agent/TimeUtil", "getExclusiveTime", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V", false); } }; } return mv; } }
4.TimeClassVisitor记录时间帮助类,代码如下:
public class TimeUtil { private static Map<String, Long> startTimes = new HashMap<String, Long>(); private static Map<String, Long> endTimes = new HashMap<String, Long>(); private TimeUtil() { } public static long getStartTime(String key) { return startTimes.remove(key); } public static void setStartTime(String key) { startTimes.put(key, System.currentTimeMillis()); } public static long getEndTime(String key) { return endTimes.remove(key); } public static void setEndTime(String key) { endTimes.put(key, System.currentTimeMillis()); } public static void getExclusiveTime(String className, String methodName, String methodDesc) { String key = className + methodName + methodDesc; long exclusive = getEndTime(key) - getStartTime(key); System.out.println(className.replace("/", ".") + "." + methodName + " exclusive:" + exclusive); } }
题记
上面的代码难免有
bug,如果你发现代码写的有问题,请你帮忙指出,让我们一起进步,让代码变的更漂亮和健壮;
顺便打点广告,如果看后对字节码技术感兴趣,欢迎加入我们(oneapm)一起做点有意思事情,可直接联系我;
完整代码请访问github;
下一篇结合
demo在深入研究
ClassVisitor
相关文章推荐
- iOS中 JSON格式文件的写入和读取
- JS正则表达式大全
- vs2010的帮助文档
- JavaScript 执行环境及作用域
- Linux sysfs device_attribute
- iOS 深拷贝和浅拷贝
- 02计算机优秀毕业论文-摘要·前言
- 添加VisualStadio的Mainbuild-Auto
- List之Stack源码分析
- RC522 SPI驱动 mini2440
- attr 和 prop 的区别
- List之Stack源码分析
- vmware 中ubuntu客户机 安装vmware tool vmhgfs 共享文件夹失败处理
- Excel2013无法打开HTTPS网站的xls文件
- 百思不得姐项目学习总结
- Android studio reset adb操作
- hrbust 1658,哈理工oj 1658 一笔画【并查集过】
- git push origin master fatal: 'origin' does not appear to be a git repository fatal: Could not read
- 学C++就学服务端,先把apue和unp两卷看了,接着libevent,出来找工作应该没问题
- 115个Java面试题和答案——(下)