什么是字节码?
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程序性能优化公众号。
本文转自 OneAPM 官方博客
相关推荐
字节码执行与JVM的内存模型密切相关,包括堆、栈、方法区、本地方法栈等。理解这些概念有助于分析和解决内存泄漏、性能瓶颈等问题。 8. **异常处理与字节码**: 字节码中的异常处理机制通过异常表实现,记录了...
通过在方法的入口和出口处插入特定的字节码,我们可以实现对方法执行过程的干预,比如添加日志、性能统计、事务处理等。ASM库使得这个过程变得相对简单,因为开发者可以直接操纵字节码,而无需理解底层的二进制格式...
1. 操作数栈:字节码指令执行时会操作一个动态栈,用于存放计算过程中的中间结果。 2. 局部变量表:每个方法都有一个局部变量表,用于存储方法参数和局部变量。 3. 方法区:存储类的元数据,如类名、方法信息等。 4....
3. 插入字节码:对于每个符合条件的方法,ASM会在其开始和结束处插入计时代码,计算并记录方法的执行时间。 4. 打印日志:在应用运行时,这些插入的计时代码会输出到控制台,开发者可以通过查看日志来分析性能。 这...
Java字节码是Java虚拟机(JVM)执行的低级指令集,它是Java源代码在编译后的结果。深入理解字节码对于优化代码性能、编写高效中间层代码(如字节码级别的库)以及理解JVM的工作原理至关重要。本教程将探讨Java字节码...
本篇将深入探讨基于Java字节码的大型应用回归测试信息处理方法。 Java字节码是Java虚拟机(JVM)运行的基础,它是由Java源代码编译生成的中间表示。由于Java字节码独立于具体的硬件平台,使得Java程序具有跨平台的...
**JByteMod-Beta:Java字节码编辑器** JByteMod-Beta是一款强大的Java字节码编辑器,专为开发者设计,旨在提供一个高效、...通过掌握这个工具,开发者能更深入地理解Java字节码,从而提升编程技能和解决问题的能力。
Java虚拟机(JVM)是Java程序运行的核心,它通过执行字节码来实现Java的跨平台特性。在Java源代码被编译成.class文件后,每个方法内部的代码会被转化为...通过分析字节码,开发者可以更深入地了解Java程序的运行机制。
7. **数据流和控制流分析**:通过深入分析字节码,可以构建数据流图和控制流图,进一步了解程序的执行逻辑和数据依赖。 这种方法的优势在于,即使没有源代码,也能获取到相当多的设计信息,有利于进行代码审计、...
### Java字节码成本分析概览 #### 一、引言与背景介绍 在软件工程领域,特别是针对Java编程语言的研究中,...对于从事相关领域的研究人员和开发人员来说,深入了解Java字节码的成本分析方法和技术将是十分有益的。
这个程序已经完成了编译过程,产生了字节码文件,使得用户可以直接运行而无需额外的编译步骤。字节码是Java虚拟机(JVM)能理解的一种中间语言,它使得Java程序具有“一次编写,到处运行”的跨平台特性。 在Java...
《编码---隐匿在计算机软硬件背后的语言》是程序员领域内一本备受推崇的经典著作,它深入浅出地揭示了计算机科学的基本原理,帮助读者理解计算机系统如何处理信息。这本书的上册主要涵盖了从二进制到高级编程语言的...
本文将深入探讨类文件结构、字节码指令、编译期处理、类加载阶段、类加载器以及运行期优化。 1. **类文件结构** Java 类文件是二进制格式的,其结构遵循特定规范。以 HelloWorld.class 文件为例,文件头的四个字节...
1. **操作数栈管理**:大部分字节码指令都会从操作数栈中取出操作数,执行计算操作,然后将结果压回栈。例如,`iload` 指令用于将局部变量表中的一个int值加载到操作数栈。 2. **局部变量表**:每个方法都有自己的...
接收方接收到数据后,也执行同样的CRC计算,并将结果与接收到的校验码比较。如果两者匹配,说明数据传输无误;如果不匹配,说明可能存在错误,需要重新传输。 总结来说,CRC16 MODBUS校验算法实现涉及了字符串和...
它具有平台无关性,能够在不同的操作系统上运行相同的应用程序,这是因为JVM会将字节码转换为特定平台的机器码执行。这种特性使得Java成为了“一次编写,到处运行”的理想编程语言。 ### JVM的架构与组成 JVM主要...
双字节BCD码则意味着两个这样的编码组成一个较大的数值,通常用于存储和处理需要精确十进制计算的场合。 实验的目的是通过实际操作来学习和巩固相关知识。首先,学生需要了解wave6000软件,这是一个常用于嵌入式...
字节码是由JVM解释器执行的二进制指令集,每个字节码指令代表一个操作,如加载常量、执行算术运算、调用方法等。通过将源代码转换为字节码,程序可以在任何支持JVM的平台上运行,实现“一次编写,到处运行”的目标。...
JVM通过字节码执行Java程序,理解字节码有助于深入优化。关键概念有: - **字节码指令**:每个字节码代表一种操作,如`aload`加载引用,`invokevirtual`调用虚方法。 - **编译优化**:JIT(Just-In-Time)编译器将...