当前位置: 首页 > news >正文

深入字节码 -- 计算方法执行时间

2019独角兽企业重金招聘Python工程师标准>>> hot3.png

##什么是字节码?

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;)J", 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.get(key);
        }
    
        public static void setStartTime(String key) {
            startTimes.put(key, System.currentTimeMillis());
        }
    
        public static long getEndTime(String key) {
            return endTimes.get(key);
        }
    
        public static void setEndTime(String key) {
            endTimes.put(key, System.currentTimeMillis());
        }
    
        public static long 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);
            return exclusive;
        }
    }

##题记

  • 上面的代码难免有bug,如果你发现代码写的有问题,请你帮忙指出,让我们一起进步,让代码变的更漂亮和健壮;
  • 顺便打点广告,如果看后对字节码技术感兴趣,欢迎加入我们oneapm,一起做点有意思事情,可直接联系我;
  • 完整代码请访问github;
  • 下一篇结合demo再深入研究ClassVisitor

OneAPM 为您提供端到端的 Java 应用性能解决方案,我们支持所有常见的 Java 框架及应用服务器,助您快速发现系统瓶颈,定位异常根本原因。分钟级部署,即刻体验,Java 监控从来没有如此简单。想技术文章,请访问 OneAPM 官方技术博客,还可以扫码关注下方的Java程序性能优化公众号。

Java程序性能优化

本文转自 OneAPM 官方博客

转载于:https://my.oschina.net/oneapmofficial/blog/625918

相关文章:

  • Building QT projects from the command line
  • http 状态码(转载)
  • 集成Diagram Viewer跟踪流程
  • PHP 7.0.4 Windows升级
  • 发现一个nginx LUA开发Web App的框架
  • golang笔记——string
  • 【转】Android Support v4、v7、v13的区别和应用场景
  • 【HDOJ】4326 Game
  • js 定义类对象
  • Android 解决onInterceptTouchEvent只能拦截到Down事件
  • 本地使用SVN编辑发布远程SAE工程
  • Oracle_spatial的空间操作符介绍
  • 用JavaScript代码实现Excel表格的办法
  • 今天开始学习python
  • Robotium入门
  • [笔记] php常见简单功能及函数
  • 【Leetcode】104. 二叉树的最大深度
  • 【跃迁之路】【641天】程序员高效学习方法论探索系列(实验阶段398-2018.11.14)...
  • fetch 从初识到应用
  • input的行数自动增减
  • js正则,这点儿就够用了
  • JWT究竟是什么呢?
  • Linux下的乱码问题
  • mac修复ab及siege安装
  • SQLServer插入数据
  • use Google search engine
  • Vue2 SSR 的优化之旅
  • 高度不固定时垂直居中
  • 缓存与缓冲
  • 看域名解析域名安全对SEO的影响
  • 聊聊springcloud的EurekaClientAutoConfiguration
  • 数组的操作
  • 吐槽Javascript系列二:数组中的splice和slice方法
  • 问题之ssh中Host key verification failed的解决
  • 小程序开发之路(一)
  • 一些关于Rust在2019年的思考
  • # 睡眠3秒_床上这样睡觉的人,睡眠质量多半不好
  • #if #elif #endif
  • #Js篇:单线程模式同步任务异步任务任务队列事件循环setTimeout() setInterval()
  • #Lua:Lua调用C++生成的DLL库
  • #QT(一种朴素的计算器实现方法)
  • $.type 怎么精确判断对象类型的 --(源码学习2)
  • (31)对象的克隆
  • (C++)栈的链式存储结构(出栈、入栈、判空、遍历、销毁)(数据结构与算法)
  • (k8s中)docker netty OOM问题记录
  • (pojstep1.1.2)2654(直叙式模拟)
  • (规划)24届春招和25届暑假实习路线准备规划
  • (过滤器)Filter和(监听器)listener
  • (黑客游戏)HackTheGame1.21 过关攻略
  • (五) 一起学 Unix 环境高级编程 (APUE) 之 进程环境
  • (转) SpringBoot:使用spring-boot-devtools进行热部署以及不生效的问题解决
  • (状压dp)uva 10817 Headmaster's Headache
  • .bat批处理(二):%0 %1——给批处理脚本传递参数
  • .NET Entity FrameWork 总结 ,在项目中用处个人感觉不大。适合初级用用,不涉及到与数据库通信。
  • .Net 垃圾回收机制原理(二)