资讯专栏INFORMATION COLUMN

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

娣辩孩 / 3005人阅读

摘要:什么是字节码程序通过编译之后生成文件就是字节码集合正是有这样一种中间码字节码,使得等函数语言只用实现一个编译器即可运行在上。

什么是字节码?

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."":()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("") && 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 startTimes = new HashMap();
        private static Map endTimes   = new HashMap();
    
        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程序性能优化公众号。

本文转自 OneAPM 官方博客

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/65531.html

相关文章

  • 局部性原理——各类优化的基石

    摘要:基于局部性原理,计算机处理器在设计时做了各种优化,比如现代的多级分支预测有良好局部性的程序比局部性差的程序运行得更快。目前计算机设计中,都是以块页为单位管理调度存储,其实就是在利用空间局部性来优化性能。   学过计算机底层原理、了解过很多架构设计或者是做过优化的同学,应该很熟悉局部性原理。即便是非计算机行业的人,在做各种调优、提效时也不得不考虑到局部性,只不过他们不常用局部性一词。如果...

    MadPecker 评论0 收藏0
  • 使用javap深入理解Java整型常量和整型变量的区别

    摘要:我下图代码第五行和第九行分别定义了一个整型变量和一个整型常量程序员都知道两者的区别。下面我们就用将文件反编译出来然后深入研究里整型变量和整型常量的区别。 我下图代码第五行和第九行分别定义了一个整型变量和一个整型常量: static final int number1 = 512; static int number3 = 545; Java程序员都知道两者的区别。 showImg(ht...

    Mr_houzi 评论0 收藏0
  • 深入理解计算机系统》·深入浅出·快速理解第2章(3日完稿)

    摘要:写在前面博客主页的江湖背景的江湖背景欢迎关注点赞收藏留言本文由原创,首发首发时间年月日最新更新时间年月日坚持和努力一定能换来诗与远方向未见花闻学习参考书籍深入理解计算机系统作者水平很有限,如果发现错误,请留言轰炸哦万分感谢感谢感谢 ?写在前面 ?博客主页:kikoking的江湖背景?...

    YJNldm 评论0 收藏0
  • 深入理解Java虚拟机到底是什么

    摘要:由虚拟机加载的类,被加载到虚拟机内存中之后,虚拟机会读取并执行它里面存在的字节码指令。虚拟机中执行字节码指令的部分叫做执行引擎。 什么是Java虚拟机? 作为一个Java程序员,我们每天都在写Java代码,我们写的代码都是在一个叫做Java虚拟机的东西上执行的。但是如果要问什么是虚拟机,恐怕很多人就会模棱两可了。在本文中,我会写下我对虚拟机的理解。因为能力所限,可能有些地方描述的不够欠...

    宋华 评论0 收藏0

发表评论

0条评论

娣辩孩

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<