ASM是操作字节码的类库,但并不是唯一的,还有许多其它的操作字节码的类库。
字节码类库,为了方便于人们对于字节码(ByteCode)内容的操作,逐渐衍生出了各种操作字节码的类库。
常见的字节码类库:Javassist、ObjectWeb-ASM、Byte Buddy(在ASM基础上实现的一个类库)
ASM是其中一种,它的特点是执行速度快、占用空间小。fast and small。
ASM
ASM的应用
- ASM所处理对象是字节码数据,也可以直观的理解成.class文件,ASM并不是针对.java文件进行处理。
- ASM能够对字节码数据进行哪些操作呢?回答:generate、transform、analyze。
- JDK当中的Lambda表达式的实现,就是借助于ASM生成匿名内部类来实现的。
- 实现原理:使用ClassWriter来生成一个匿名内部类并实现特定的接口(java/util/function/BiFunction),在接口定义的方法(apply)实现中包含Lambda表达式的内容,从而将lambda表达式的代码包装起来。
- 具体实现在InnerClassLambdaMetafactory.spinInnerClass()方法中。
ASM的组成部分
ASM由Core API和Tree API两个部分组成。
每个组成部分由多个.jar文件组成,每个.jar文件里包含多个具体的类文件(.class)。
- core-API
- asm.jar
- ClassReader类,负责读取.class文件里的内容,然后拆分成各个不同的部分。
- ClassVisitor类,负责对.class文件中某一部分里的信息进行修改。
- ClassWriter类,负责将各个不同的部分重新组合成一个完整的.class文件。
- asm-util.jar
- 主要包含的是一些工具类。
- 提供的是通用性的功能,没有特别明确的应用场景。
- asm-commons.jar
- 主要包含的是一些常用功能类。
- 提供的功能,都是为解决某一种特定场景中出现的问题而提出的解决思路。
- asm.jar
【侵权删,侵权删,侵权删】
Java ClassFile
从JVM规范的角度,来理解.class文件的结构,从而理解ASM中方法和参数的含义。 详见虚拟机部分以及内存区域部分。
ClassFile
- 在ClassFile结构中,每一个方法都对应一个method_info结构。
- 在method_info结构中,方法体的代码存储在Code结构内。
- 在Code结构中,包含有 max_stack、max_locals,分别代表了操作数栈深度的最大值 以及 局部变量表所需要的存储空间。
- 在Code结构中,属性表中的 StackMapTable 结构,用于存储关于栈帧frame的变化,主要作用是对字节码ByteCode进行类型检查。
- 该结构包含了零至多个栈映射帧。每个栈映射帧,都显示或者隐式地代表一个字节码偏移量,用于表示执行到该字节码时局部变量表和操作数栈的验证类型。
- 在类加载过程的验证阶段-字节码验证时,类型检查器会通过检查目标方法的局部变量和操作数栈所需要的类型来确定一段字节码指令是否符合逻辑约束。
ClassFile对方法的限制
在ClassFile结构中,对于方法接收的参数和方法体的大小是有数量限制的。
- 方法参数的限制:在一个方法当中,方法接收的参数最多有255个。
- 注意1:非静态方法中,this也要占用1个参数位置
- 注意2:不管是non-static方法,还是static方法,long类型或double类型占据2个参数位置。
- 因此可以知道,实际可接收的参数数量要小于255。
- 方法体的大小限制:方法体内最多包含65535个字节。
- 当方法体的代码超过65535字节(bytes)时,会出现编译错误:code too large。
ClassVisitor
负责对.class文件中某一部分里的信息进行修改。
- 在ClassVisitor类当中,定义了许多visitXxx()方法,这些方法的调用要遵循一定的顺序。
- 只关注ClassVisitor类当中的visit()方法、visitField()方法、visitMethod()方法和visitEnd()方法这4个方法。
- 方法调用顺序可以简化如下:先调用visit()方法,接着调用visitField()方法或visitMethod()方法,最后调用visitEnd()方法。
- 在ClassVisitor类当中,每一个visitXxx()方法中的方法参数,都与ClassFile结构密切相关。
ClassWriter:ClassVistor
负责将各个不同的部分重新组合成一个完整的.class文件。
- 在创建ClassWriter实例对象时,需要指定一个flags参数
- 选值1:0。
- 此时ASM不会自动计算max stacks、max locals、stack map frames。
- 使用者必须要提供正确的max stacks、max locals和stack map frame的值。
- 选值2:ClassWriter.COMPUTE_MAXS。
- 此时ASM会自动计算max stacks、max locals,但不会自动计算stack map frames。 使用者需要提供正确的stack map frame的值。
- 选值3:ClassWriter.COMPUTE_FRAMES(推荐使用)。
- 此时ASM会自动计算max stacks、max locals、stack map frames。
由上述可知,方法会作为 ClassFile 的 method_info 中的一个而存在,method_info中的Code属性用于存储方法体的代码以及相关的数据信息,其中包含有 max_stack、max_locals;并且通过Code结构中的属性表中包含的 StackMapTable 结构来存储所有栈帧的变化,主要作用是对字节码ByteCode进行类型检查。
因此可以通过将flags参数设置为ClassWriter.COMPUTE_MAXS,那么ASM会自动帮助我们计算Code结构中max_stack和max_locals的值。
因此可以通过将flags参数设置为ClassWriter.COMPUTE_FRAMES,那么ASM会自动帮助我们计算Code结构中max_stack和max_locals的值,以及属性表里面的StackMapTable的内容。
ClassWriter的使用
负责将各个不同的部分重新组合成一个完整的.class文件。
第一步,创建ClassWriter对象。
第二步,调用ClassWriter对象的visitXxx()方法。
- 这些visitXxx()方法的调用,就是在为构建ClassFile提供“原材料”的过程。
- 方法调用顺序:
- 先调用visit()方法
- 接着调用visitField()方法或visitMethod()方法来访问字段或访问方法
- 最后调用visitEnd()方法
- ClassWriter.visitField()方法会返回一个FieldVisitor对象,要记得调用其对应的visitEnd()方法。
- ClassWriter.visitMethod()方法会返回一个MethodVisitor对象,要记得调用其对应的visitEnd()方法。
第三步,调用ClassWriter对象的toByteArray()方法。 这个方法的作用是将“所有的努力”(对visitXxx()的调用)转换成byte[],而这些byte[]的内容就遵循ClassFile结构。
ClassWriter.toByteArray()
ClassWriter.toByteArray()通过三个步骤来得到byte[]:
-
第一步,计算size大小。
- 这个size就是表示byte[]的最终的长度是多少。
-
第二步,将数据填充到byte[]当中。
-
第三步,将byte[]数据返回。
文末
我总结了一些Android核心知识点,以及一些最新的大厂面试题、知识脑图和视频资料解析。
需要的小伙伴点击文末小卡片直接可以领取哦!我免费分享给你,以后的路也希望我们能一起走下去。(谢谢大家一直以来的支持,需要的自己领取)
Android学习PDF+架构视频+面试文档+源码笔记
部分资料一览:
- 330页PDF Android学习核心笔记(内含8大板块)
- Android学习的系统对应视频
- Android进阶的系统对应学习资料
- Android BAT大厂面试题(有解析)