`
OneAPM_Official
  • 浏览: 22940 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

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

阅读更多

什么是字节码?

java程序通过javac编译之后生成文件.class就是字节码集合,正是有这样一种中间码(字节码),使得scala/groovy/clojure等函数语言只用实现一个编译器即可运行在JVM上。 
看看一段简单代码。

 
  1. 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

 
  1.     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方法,代码如下:

 
  1.     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实现一个转换器,代码如下:

 
  1.         //字节码转化器类
        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类访问器,实现字节码修改,代码如下:

 
  1.         //定义扫描待修改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记录时间帮助类,代码如下:

 
  1.   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 官方博客

 

 
分享到:
评论

相关推荐

    深入理解java类加载机制

    我们将详细讲解字节码文件的结构、语法和格式,以及字节码指令的定义和应用,并通过大量实例进行编程详解,帮助开发人员深入了解Java字节码的使用方法和技巧。 在类加载方面,我们将深入探讨Java程序的类加载原理和...

    深入Java虚拟机.pdf

    JVM是java的核心和基础,在java编译器和os平台之间的虚拟处理器。它是一种基于下层的操作系统和硬件平台并利用软件方法来实现的抽象的计算机,可以在上面执行java的字节码程序

    计算机应基础与应用复习题.doc

    42.Windows具有一套十分有用的帮助系统,可以帮助用户深入了解Windows的功能和 使用方法。 43.启动计算机,进入Windows XP界面,在"显示属性"对话框中可以更改桌面的背景、主题、外观、分辨率等。 44.一般显示器...

    计算机二级公共基础知识

    例如,长度为8的线性表关键码序列为:[6,13,27,30,38,46,47,70],被查元素为38,首先将与线性表的中间项比较,即与第4个数据元素30相比较,38大于中间项30的值,则在线性表[38,46,47,70]中继续查找;...

    uboott移植实验手册及技术文档

    了解 U-Boot-1.3.1 的代码结构,掌握其移植方法。 【实验环境】 1、Ubuntu 7.0.4发行版 2、u-boot-1.3.1 3、FS2410平台 4、交叉编译器 arm-softfloat-linux-gnu-gcc-3.4.5 【实验步骤】 一、建立自己的平台...

    CLR.via.C#.(中文第3版)(自制详细书签)Part2

    《CLR via C#(第3版) 》针对.NET Framework 4.0和多核编程进行了全面更新和修订,是帮助读者深入探索和掌握公共语言运行时、C#和.NET开发的重要参考,同时也是帮助开发人员构建任何一种应用程序(如Microsoft ...

    代码优化:有效使用内存.part3

    本书系统深入地介绍了各种代码优化编程技术。全书分为4章。第1章集中介绍如何确定程序中消耗CPU时钟最多的热点代码的所谓程序剖析技术以及典型部分工具的实用知识。第2,3章分别全面介绍RAM了系统与高速缓存子系统的...

    代码优化:有效使用内存.part1

    本书系统深入地介绍了各种代码优化编程技术。全书分为4章。第1章集中介绍如何确定程序中消耗CPU时钟最多的热点代码的所谓程序剖析技术以及典型部分工具的实用知识。第2,3章分别全面介绍RAM了系统与高速缓存子系统的...

    代码优化:有效使用内存.part2

    本书系统深入地介绍了各种代码优化编程技术。全书分为4章。第1章集中介绍如何确定程序中消耗CPU时钟最多的热点代码的所谓程序剖析技术以及典型部分工具的实用知识。第2,3章分别全面介绍RAM了系统与高速缓存子系统的...

    CLR.via.C#.(中文第3版)(自制详细书签)Part1

    《CLR via C#(第3版) 》针对.NET Framework 4.0和多核编程进行了全面更新和修订,是帮助读者深入探索和掌握公共语言运行时、C#和.NET开发的重要参考,同时也是帮助开发人员构建任何一种应用程序(如Microsoft ...

    CLR.via.C#.(中文第3版)(自制详细书签)

    25.7 使用专用线程执行异步的计算限制操作 25.8 使用线程的理由 25.9 线程调度和优先级 25.10 前台线程和后台线程 25.11 继续学习 第26章 计算限制的异步操作 26.1 CLR线程池基础 26.2 执行简单的计算限制...

    CLR.via.C#.(中文第3版)(自制详细书签)Part3

    《CLR via C#(第3版) 》针对.NET Framework 4.0和多核编程进行了全面更新和修订,是帮助读者深入探索和掌握公共语言运行时、C#和.NET开发的重要参考,同时也是帮助开发人员构建任何一种应用程序(如Microsoft ...

    课程设计简易计算器设计

    要通过数码管显示出当前数值,还必须将BCD码进一步转换为七段码,转换的最终结果数据存放于显示缓冲区30H-33H单元中,其中30H单元存放数值的个位七段码,31H单元存放数值的十位七段码,32H单元存放数值的百位七段码...

    基于沙盒的Android恶意软件检测技术研究

    另外,考虑到移动终端的局限性和检测过程的可靠性,在移动终端经过预处理后,使用高效的比对算法进行二进制比对,同时通过网络将APK中的class字节码文件上传到云端,通过在云端的虚拟机中运行执行代码进行进一步检测...

    单片机课程设计——计算器设计.doc

    随机存储器ROM,多种 I/O口和中断系统、定时器/计时器等功能集成到一块硅片上构成的一个小而完善的计算 机系统——单片机,配以汇编语言编写的执行程序,能更好的解决计算机计算的问题,随 着数字生活的到来,单片机...

    单片机课程设计——计算器设计(1).doc

    随机存储器ROM,多种 I/O口和中断系统、定时器/计时器等功能集成到一块硅片上构成的一个小而完善的计算 机系统——单片机,配以汇编语言编写的执行程序,能更好的解决计算机计算的问题,随 着数字生活的到来,单片机...

    宋劲彬的嵌入式C语言一站式编程

    5. 深入理解函数 1. return语句 2. 增量式开发 3. 递归 6. 循环语句 1. while语句 2. do/while语句 3. for语句 4. break和continue语句 5. 嵌套循环 6. goto语句和标号 7. 结构体 1. 复合类型与结构体 2. 数据抽象 3...

    C#微软培训资料

    第四部分 深入了解 C#.174 第十五章 接 口 .174 15.1 组件编程技术 .174 15.2 接 口 定 义 .177 15.3 接口的成员 .178 15.4 接口的实现 .182 15.5 抽象类与接口 .195 15.6 小 结 .196 第十六章 组织...

    西安理工大学 微机原理课件

    学习程序设计应从基本知识和基本方法入手,逐步深入。本章中讲述了伪指令,汇编语言源程序格式,汇编语言程序设计基本方法,程序设计举例等内容。使大家能掌握程序设计的方法,提高对汇编语言的运用能力,并掌握相关...

    Reversing:逆向工程揭密

    1.4.3 虚拟机和字节码 12 1.4.4 操作系统 13 1.5 逆向过程 13 1.5.1 系统级逆向 14 1.5.2 代码级逆向 14 1.6 工具 14 1.6.1 系统监控工具 15 1.6.2 反汇编器 15 1.6.3 调试器 15 1.6.4 反编译器 16 1.7 逆向合法吗?...

Global site tag (gtag.js) - Google Analytics