ASM源码学习之ClassReader、ClassVisitor与ClassWriter详解

ASM

ASM是Java中比较流行的用来读写字节码的类库,用来基于字节码层面对代码进行分析和转换。在读写的过程中可以加入自定义的逻辑以增强或修改原来已编译好的字节码,比如CGLIB用它来实现动态代理。ASM被设计用于在运行时对Java类进行生成和转换,当然也包括离线处理。ASM短小精悍、且速度很快,从而避免在运行时动态生成字节码或转换时对程序速度的影响,又因为它体积小巧,可以在很多内存受限的环境中使用。

ASM的主要优势包括如下几个方面:

1. 它又一个很小,但设计良好并且模块化的API,且易于使用。

2. 它具有很好的文档,并且还有eclipse插件。

3. 它支持最新的Java版本。

4. 它短小精悍、快速、健壮。

5. 它又一个很大的用户社区,可以给新用户提供支持。

6. 它的开源许可允许你几乎以任何方式来使用它。

关于ASM的详细介绍可以参考:java字节码框架ASM的深入学习

ASM Core设计一览

在ASM的核心实现中,它主要有以下几个类、接口(在org.objectweb.asm包中):

ClassReader类:字节码的读取与分析引擎。它采用类似SAX的事件读取机制,每当有事件发生时,调用注册的ClassVisitor、AnnotationVisitor、FieldVisitor、MethodVisitor做相应的处理。

ClassVisitor接口:定义在读取Class字节码时会触发的事件,如类头解析完成、注解解析、字段解析、方法解析等。

AnnotationVisitor接口:定义在解析注解时会触发的事件,如解析到一个基本值类型的注解、enum值类型的注解、Array值类型的注解、注解值类型的注解等。

FieldVisitor接口:定义在解析字段时触发的事件,如解析到字段上的注解、解析到字段相关的属性等。

MethodVisitor接口:定义在解析方法时触发的事件,如方法上的注解、属性、代码等。

ClassWriter类:它实现了ClassVisitor接口,用于拼接字节码。

AnnotationWriter类:它实现了AnnotationVisitor接口,用于拼接注解相关字节码。

FieldWriter类:它实现了FieldVisitor接口,用于拼接字段相关字节码。

MethodWriter类:它实现了MethodVisitor接口,用于拼接方法相关字节码。

SignatureReader类:对类定义、字段定义、方法定义、本地变量定义的签名的解析。Signature因范型引入,用于存储范型定义时的元数据(因为这些元数据在运行时会被擦除)。

SignatureVisitor接口:定义在解析Signature时会触发的事件,如正常的Type参数、类或接口的边界等。

SignatureWriter类:它实现了SignatureVisitor接口,用于拼接范型相关字节码。

Attribute类:字节码中属性的类抽象。

ByteVector类:字节码二进制存储的容器。

Opcodes接口:字节码指令的一些常量定义。

Type类:类型相关的常量定义以及一些基于其上的操作。

他们之间的类图关系如下:

ClassReader是ASM中最核心的实现,它用于读取并解析Class字节码。

在构建ClassReader实例时,它首先保存字节码二进制数组b,然后创建items数组,数组的长度在字节码数组的第8、9个字节指定(最前面4个字节是魔数CAFEBABE,之后2个字节是次版本号,再后2个字节是主版本号),每个item表示常量池项在字节码数组的偏移量加1(常量池中每个项由1个字节的type和紧跟的字节数组表示,常量池项有12种类型,其中CONSTANT_FieldRef_Info、CONSTANT_MethodRef_Info、CONSTANT_InterfaceMethodRef_Info、CONSTANT_NameAndType_Info包括其类型字节占用5个字节,另外4个字节每2个字节为字段、方法等所在的类、其名称、描述符在当前常量池中CONSTANT_Utf8_Info类型的引用;CONSTANT_Integer_Info、CONSTANT_Float_Info包括其类型字节占用5个字节,另外四个字节为其对应的值;CONSTANT_Class_Info、CONSTANT_String_Info包括其类型字节占用3个字节,另外两个字节为在当前常量池CONSTANT_Utf8_Info项的索引;CONSTANT_Utf8_Info类型第1个字节表示类型,第2、3个字节为该项所表示的字符串的长度);CONSTANT_Double_Info、CONSTANT_Long_Info加类型字节为9个字;maxStringLength表示最长的UTF8类型的常量池项的值,用于决定在解析CONSTANT_Utf8_Info类型项时最大需要的字符数组;header表示常量池之后的字节码的第一个字节。

在调用ClassReader的accept方法时,它解析字节码中常量池之后的所有元素。紧接着常量池的2个字节是该类的access标签:ACC_PUBLIC、ACC_FINAL等;之后2个字节为当前类名在常量池CONSTANT_Utf8_Info类型的索引;之后2个字节为其父类名在常量池CONSTANT_Utf8_Info类型的索引(索引值0表示父类为null,即直接继承自Object类);再之后为其实现的接口数长度和对应各个接口名在常量池中CONSTANT_Utf8_Info类型的索引值;暂时先跳过Field和Method定义信息,解析类的attribute表,它用两个字节表达attribute数组的长度,每个attribute项中最前面2个字节是attribute名称:SourceFile(读取sourceFile值)、InnerClasses(暂时纪录起始索引)、EnclosingMethod(纪录当前匿名类、本地类包含者类名以及包含者的方法名和描述符)、Signature(类的签名信息,用于范型)、RuntimeVisibleAnnotations(暂时纪录起始索引)、Deprecated(表识属性)、Synthetic(标识属性)、SourceDebugExtension(为调试器提供的自定义扩展信息,读取成一个字符串)、RuntimeInvisibleAnnotations(暂时纪录起始索引),对其他不识别的属性,纪录成Attribute链,如果attribute名称符合在accept中attribute数组中指定的attribute名,则替换传入的attribute数组对应的项;根据解析出来的信息调用以下visit方法:

void visit(int version, int access, String name, String signature, String superName, String[] interfaces);
// sourceFile, sourceDebug
void visitSource(String source, String debug);
// EnclosingMethod attribute: enclosingOwner, enclosingName, enclosingDesc.
// Note: only when the class has EnclosingMethod attribute, meaning the class is a local class or an anonymous class
void visitOuterClass(String owner, String name, String desc);

依次解析RuntimeVisibleAnnotations和RuntimeInvisibleAnnotations属性,首先解析定义的Annotation的描述符以及运行时可见flag,返回用户自定义的AnnotationVisitor:

AnnotationVisitor visitAnnotation(String desc, boolean visible);

对每个定义的Annotation,解析其键值对,并根据不同的Annotation字段值调用AnnotationVisitor中的方法,在所有解析结束后,调用AnnotationVisitor.visitEnd方法:

public interface AnnotationVisitor {
 // 对基本类型的数组,依然采用该方法,visitArray只是在非基本类型时调用。
 void visit(String name, Object value);
 void visitEnum(String name, String desc, String value);
 AnnotationVisitor visitAnnotation(String name, String desc);
 AnnotationVisitor visitArray(String name);

 void visitEnd();
}

之前解析出的attribute链表(非标准的Attribute定义),对每个Attribute实例,调用ClassVisitor中的visitAttribute方法:

void visitAttribute(Attribute attr);

Attribute类包含type字段和一个字节数组:

public class Attribute {
 public final String type;
 byte[] value;
 Attribute next;
}

对每个InnerClasses属性,解析并调用ClassVisitor的visitInnerClass方法(该属性事实上保存了所有其直接内部类以及它本身到最顶层类的路径):

void visitInnerClass(String name, String outerName, String innerName, int access);

解析字段,它紧跟接口数组定义之后,最前面的2个字节为字段数组的长度,对每个字段,前面2个字节为访问flag定义,再后2个字节为Name索引,以及2个字节的描述符索引,然后解析其Attribute信息:ConstantValue、Signature、Deprecated、Synthetic、RuntimeVisibleAnnotations、RuntimeInvisibleAnnotations以及非标准定义的Attribute链,而后调用ClassVisitor的visitField方法,返回FieldVisitor实例:

// 其中value为静态字段的初始化值(对非静态字段,它的初始化必须由构造函数实现),如果没有初始化值,该值为null。
FieldVisitor visitField(int access, String name, String desc, String signature, Object value);

对返回的FieldVisitor依次对其Annotation以及非标准Attribute解析,调用其visit方法,并在完成后调用它的visitEnd方法:

public interface FieldVisitor {
 AnnotationVisitor visitAnnotation(String desc, boolean visible);
 void visitAttribute(Attribute attr);
 void visitEnd();
}

解析方法定义,它紧跟字段定义之后,最前面的2个字节为方法数组长度,对每个方法,前面2个字节为访问flag定义,再后2个字节为Name索引,以及2个字节的方法描述符索引,然后解析其Attribute信息:Code、Exceptions、Signature、Deprecated、RuntimeVisibleAnnotations、AnnotationDefault、Synthetic、RuntimeInvisibleAnnotations、RuntimeVisibleParameterAnnotations、RuntimeInvisibleParameterAnnotations以及非标准定义的Attribute链,如果存在Exceptions属性,解析其异常类数组,之后调用ClassVisitor的visitMethod方法,返回MethodVisitor实例:

MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions);

AnnotationDefault为对Annotation定义时指定默认值的解析;然后依次解析RuntimeVisibleAnnotations、RuntimeInvisibleAnnotations、RuntimeVisibleParameterAnnotations、RuntimeInvisibleParameterAnnotations等属性,调用相关AnnotationVisitor的visit方法;对非标准定义的Attribute链,依次调用MethodVisitor的visitAttribute方法:

public interface MethodVisitor {
 AnnotationVisitor visitAnnotationDefault();
 AnnotationVisitor visitAnnotation(String desc, boolean visible);
 AnnotationVisitor visitParameterAnnotation(int parameter, String desc, boolean visible);
 void visitAttribute(Attribute attr);
}

对Code属性解析,读取2个字节的最深栈大小、最大local变量数、code占用字节数,调用MethodVisitor的visitCode()方法表示开始解析Code属性,对每条指令,创建一个Label实例并构成Label数组,解析Code属性中的异常表,对每个异常项,调用visitTryCatchBlock方法:

void visitTryCatchBlock(Label start, Label end, Label handler, String type);

Label包含以下信息:

/**
 * A label represents a position in the bytecode of a method. Labels are used
 * for jump, goto, and switch instructions, and for try catch blocks.
 *
 * @author Eric Bruneton
 */
public class Label {
 public Object info;
 int status;
 int line;
 int position;
 private int referenceCount;
 private int[] srcAndRefPositions;
 int inputStackTop;
 int outputStackMax;
 Frame frame;
 Label successor;
 Edge successors;
 Label next;
}

解析Code属性中的内部属性信息:LocalVariableTable、LocalVariableTypeTable、LineNumberTable、StackMapTable、StackMap以及非标准定义的Attribute链,对每个Label调用其visitLineNumber方法以及对每个Frame调用visitFrame方法,并且对相应的指令调用相应的方法:

void visitFrame(int type, int nLocal, Object[] local, int nStack, Object[] stack);
// Visits a zero operand instruction.
void visitInsn(int opcode);
// Visits an instruction with a single int operand.
void visitIntInsn(int opcode, int operand);
// Visits a local variable instruction. A local variable instruction is an instruction that loads or stores the value of a local variable.
void visitVarInsn(int opcode, int var);
// Visits a type instruction. A type instruction is an instruction that takes the internal name of a class as parameter.
void visitTypeInsn(int opcode, String type);
// Visits a field instruction. A field instruction is an instruction that loads or stores the value of a field of an object.
void visitFieldInsn(int opcode, String owner, String name, String desc);
// Visits a method instruction. A method instruction is an instruction that invokes a method.
void visitMethodInsn(int opcode, String owner, String name, String desc);
// Visits a jump instruction. A jump instruction is an instruction that may jump to another instruction.
void visitJumpInsn(int opcode, Label label);
// Visits a label. A label designates the instruction that will be visited just after it.
void visitLabel(Label label);
// Visits a LDC instruction.
void visitLdcInsn(Object cst);
// Visits an IINC instruction.
void visitIincInsn(int var, int increment);
// Visits a TABLESWITCH instruction.
void visitTableSwitchInsn(int min, int max, Label dflt, Label[] labels);
// Visits a LOOKUPSWITCH instruction.
void visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels);
// Visits a MULTIANEWARRAY instruction.
void visitMultiANewArrayInsn(String desc, int dims);
// Visits a try catch block.
void visitTryCatchBlock(Label start, Label end, Label handler, String type);
void visitLocalVariable(String name, String desc, String signature, Label start, Label end, int index);
// Visits a line number declaration.
void visitLineNumber(int line, Label start);
// Visits the maximum stack size and the maximum number of local variables of the method.
void visitMaxs(int maxStack, int maxLocals);

最后调用ClassVisitor的visitEnd方法:

void visitEnd();

ClassWriter实现

ClassWriter继承自ClassVisitor接口,可以使用它调用其相应的visit方法动态的构造一个字节码类。它包含以下字段信息:

public class ClassWriter implements ClassVisitor {
 //The class reader from which this class writer was constructed, if any.
 ClassReader cr;
 //Minor and major version numbers of the class to be generated.
 int version;
 //Index of the next item to be added in the constant pool.
 int index;
 //The constant pool of this class.
 final ByteVector pool;
 //The constant pool's hash table data.
 Item[] items;
 //The threshold of the constant pool's hash table.
 int threshold;
 //A reusable key used to look for items in the {@link #items} hash table.
 final Item key;
 //A reusable key used to look for items in the {@link #items} hash table.
 final Item key2;
 //A reusable key used to look for items in the {@link #items} hash table.
 final Item key3;
 //A type table used to temporarily store internal names that will not necessarily be stored in the constant pool.
 Item[] typeTable;
 //Number of elements in the {@link #typeTable} array.
 private short typeCount;
 //The access flags of this class.
 private int access;
 //The constant pool item that contains the internal name of this class.
 private int name;
 //The internal name of this class.
 String thisName;
 //The constant pool item that contains the signature of this class.
 private int signature;
 //The constant pool item that contains the internal name of the super class of this class.
 private int superName;
 // Number of interfaces implemented or extended by this class or interface.
 private int interfaceCount;
 //The interfaces implemented or extended by this class or interface.
 private int[] interfaces;
 //The index of the constant pool item that contains the name of the source file from which this class was compiled.
 private int sourceFile;
 //The SourceDebug attribute of this class.
 private ByteVector sourceDebug;
 //The constant pool item that contains the name of the enclosing class of this class.
 private int enclosingMethodOwner;
 //The constant pool item that contains the name and descriptor of the enclosing method of this class.
 private int enclosingMethod;
 //The runtime visible annotations of this class.
 private AnnotationWriter anns;
 //The runtime invisible annotations of this class.
 private AnnotationWriter ianns;
 //The non standard attributes of this class.
 private Attribute attrs;
 //The number of entries in the InnerClasses attribute.
 private int innerClassesCount;
 //The InnerClasses attribute.
 private ByteVector innerClasses;
 //The fields of this class. These fields are stored in a linked list of {@link FieldWriter} objects, linked to each other by their {@link FieldWriter#next} field. This field stores the first element of this list.
 FieldWriter firstField;
 //This field stores the last element of this list.
 FieldWriter lastField;
 //The methods of this class. These methods are stored in a linked list of {@link MethodWriter} objects, linked to each other by their {@link MethodWriter#next} field. This field stores the first element of this list.
 MethodWriter firstMethod;
 //This field stores the last element of this list.
 MethodWriter lastMethod;
 //true if the maximum stack size and number of local variables must be automatically computed.
 private final boolean computeMaxs;
 //true if the stack map frames must be recomputed from scratch.
 private final boolean computeFrames;
 //true if the stack map tables of this class are invalid.
 boolean invalidFrames;
}

总结

以上就是这篇文章的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,如果有疑问大家可以留言交流。

(0)

相关推荐

  • JVM的垃圾回收机制详解和调优

    文章来源:matrix.org.cn 作者:ginger547 1.JVM的gc概述 gc即垃圾收集机制是指jvm用于释放那些不再使用的对象所占用的内存.java语言并不要求jvm有gc,也没有规定gc如何工作.不过常用的jvm都有gc,而且大多数gc都使用类似的算法管理内存和执行收集操作. 在充分理解了垃圾收集算法和执行过程后,才能有效的优化它的性能.有些垃圾收集专用于特殊的应用程序.比如,实时应用程序主要是为了避免垃圾收集中断,而大多数OLTP应用程序则注重整体效率.理解了应用程序的工作负荷

  • JAVA中JVM的重排序详细介绍

    在并发程序中,程序员会特别关注不同进程或线程之间的数据同步,特别是多个线程同时修改同一变量时,必须采取可靠的同步或其它措施保障数据被正确地修改,这里的一条重要原则是:不要假设指令执行的顺序,你无法预知不同线程之间的指令会以何种顺序执行. 但是在单线程程序中,通常我们容易假设指令是顺序执行的,否则可以想象程序会发生什么可怕的变化.理想的模型是:各种指令执行的顺序是唯一且有序的,这个顺序就是它们被编写在代码中的顺序,与处理器或其它因素无关,这种模型被称作顺序一致性模型,也是基于冯·诺依曼体系的模型.

  • Java虚拟机JVM性能优化(三):垃圾收集详解

    Java平台的垃圾收集机制显著提高了开发者的效率,但是一个实现糟糕的垃圾收集器可能过多地消耗应用程序的资源.在Java虚拟机性能优化系列的第三部分,Eva Andreasson向Java初学者介绍了Java平台的内存模型和垃圾收集机制.她解释了为什么碎片化(而不是垃圾收集)是Java应用程序性能的主要问题所在,以及为什么分代垃圾收集和压缩是目前处理Java应用程序碎片化的主要办法(但不是最有新意的). 垃圾收集(GC)的目的是释放那些不再被任何活动对象引用的Java对象所占用的内存,它是Java

  • Java虚拟机JVM性能优化(一):JVM知识总结

    Java应用程序是运行在JVM上的,但是你对JVM技术了解吗?这篇文章(这个系列的第一部分)讲述了经典Java虚拟机是怎么样工作的,例如:Java一次编写的利弊,跨平台引擎,垃圾回收基础知识,经典的GC算法和编译优化.之后的文章会讲JVM性能优化,包括最新的JVM设计--支持当今高并发Java应用的性能和扩展. 如果你是一个开发人员,你肯定遇到过这样的特殊感觉,你突然灵光一现,所有的思路连接起来了,你能以一个新的视角来回想起你以前的想法.我个人很喜欢学习新知识带来的这种感觉.我已经有过很多次这样

  • java字节码框架ASM的深入学习

    一.什么是ASM ASM是一个java字节码操纵框架,它能被用来动态生成类或者增强既有类的功能.ASM 可以直接产生二进制 class 文件,也可以在类被加载入 Java 虚拟机之前动态改变类行为.Java class 被存储在严格格式定义的 .class文件里,这些类文件拥有足够的元数据来解析类中的所有元素:类名称.方法.属性以及 Java 字节码(指令).ASM从类文件中读入信息后,能够改变类行为,分析类信息,甚至能够根据用户要求生成新类. 使用ASM框架需要导入asm的jar包,下载链接:

  • 深入JVM剖析Java的线程堆栈

    在这篇文章里我将教会你如何分析JVM的线程堆栈以及如何从堆栈信息中找出问题的根因.在我看来线程堆栈分析技术是Java EE产品支持工程师所必须掌握的一门技术.在线程堆栈中存储的信息,通常远超出你的想象,我们可以在工作中善加利用这些信息. 我的目标是分享我过去十几年来在线程分析中积累的知识和经验.这些知识和经验是在各种版本的JVM以及各厂商的JVM供应商的深入分析中获得的,在这个过程中我也总结出大量的通用问题模板. 那么,准备好了么,现在就把这篇文章加入书签,在后续几周中我会给大家带来这一系列的专

  • 解析Linux系统中JVM内存2GB上限的详解

    我们通常使用的JVM都是32位的(64位的JVM会损失10-20%的性能,通常不建议使用),而32位程序的寻址空间应该是4GB才对,为什么Linux上的JVM内存只能使用2GB呢? 经过和JDK研发组的人员沟通,终于弄清楚了一些相关的原因.这个问题存在于早期的一些Linux版本中,特别是内核2.5以前的版本,2.6以后的版本就基本上没有这个问题了.原来这些Linux版本对进程有个对内存2GB的限制,是一个地址连续的内存块大小的上限,而JVM的堆空间(heap size)需要连续的地址空间,因此,

  • java字节码框架ASM操作字节码的方法浅析

    之前我们已经对ASM进行的详细的介绍,需要的朋友们可以点击这里:java字节码框架ASM的深入学习 JVM的类型签名对照表 Type Signature Java Type Z boolean B byte C char S short I int J long F float D double L fully-qualified-class ;fully-qualified-class [ type type[] ( arg-types ) ret-type method type 比如,ja

  • 基于JVM 调优的技巧总结分析

    这篇是技巧性的文章,如果要找关于GC或者调整内纯的文章,看我其他几篇文章.因为是JVM 调优总结,所以废话少说.从各方面一共收集到以下几个方法:1.升级 JVM 版本.如果能使用64-bit,使用64-bit JVM.    基本上没什么好解释的,很简单将JVM升级到最新的版本.如果你还是使用JDK1.4甚至是更早的JVM,那你首先要做的就是升级.因为JVM从1.4- >1.5->1.6可不是仅仅的版本号升级,或者仅仅往里面加了一堆新的语言特性,这么简单.而是真正在JVM做了重大的改进,每次版

  • jvm内存溢出解决方法(jvm内存溢出怎么解决)

    java.lang.OutOfMemoryError: PermGen space 发现很多人把问题归因于: spring,hibernate,tomcat,因为他们动态产生类,导致JVM中的permanent heap溢出 .然后解决方法众说纷纭,有人说升级 tomcat版本到最新甚至干脆不用tomcat.还有人怀疑spring的问题,在spring论坛上讨论很激烈,因为spring在AOP时使用CBLIB会动态产生很多类. 但问题是为什么这些王牌的开源会出现同一个问题呢,那么是不是更基础的原

随机推荐