前言:文中出现的示例代码地址为:gitee代码地址
使用javap -verbose 命令分析一个.class字节码文件时(以下简称字节码文件),将会分析该字节码文件的魔数,版本号,常量池,类信息,类的构造方法,类中的方法信息,类变量与实例变量等信息。字节码文件是十六进制的数字,两个十六进制数的大小就是一个字节。
字节码文件的整体结构如下,其中的类型就是指数据类型。看不懂没关系,下面将对每一个组成部分进行细致的分析。
描述: U4类型,所有的字节码文件的前四个字节都是魔数,魔数的值是固定的,为:0xCAFEBABE。这是人家规定的,别问为什么。
描述:魔数之后的四个字节表示jdk版本号,前两个字节表示U2类型的次版本号(mihor version)后两个字节表示U2类型的主版本号(major version)。这里的为 00 00 00 34 ,换算成十进制,表示次版本号为0,主版本号为18。所以该文件的版本号为1.8.0 。
描述:表类型,紧接着主版本号之后的就是常量池入口,一个java类定义的很多信息都是由常量池进行维护和描述的,可以将常量池看作Class文件的资源仓库。比如说Java类中定义的方法和变量信息都是存储在常量池中的。常量池主要保存两类常量:字面量和符号引用。字面量如文本字符串,java中声明为final的常量值,基本数据类型的值等。符号引用如类和接口的全局限定名,字段的名称和描述符(什么是描述符?下面有讲到),方法和接口的名称和描述符等。
java类所对应的常量池主要由常量池数量和常量池数组(也称常量表,以下混用)这两部分共同构成。常量池数量紧跟在主版本号后面,占据两个字节。常量池数组则紧跟在常量池数量后面。常量池数组和一般的数组是不同的。常量池数组中的不同元素的类型,结构都是不相同的,长度也当然不相同,但是每一种元素的第一个数据都是一个u1类型,占据一个字节,该字节是一个标志位。jvm在解析常量池时,就会根据这个u1类型来获取元素的具体类型。值得注意的是,常量池数组中元素的个数 = 常量池数量 -1 (其中0暂时不用)。其根本原因在于,索引为0也是一个常量(是一个保留常量),只不过它不位于常量表中,这个常量就对应null值。所以常量池表索引从1开始而非从0开始。
编译一个MyTest1.java程序得到字节码文件。
public class MyTest1 {
private int a = 1;
public int getA() {
return a;
}
public void setA(int a) {
this.a = a;
}
}
以下结果是MyTest1.class的字节码文件根据上图常量池表数据结构的对应关系的翻译过来得到的,加了括号的是我自己添加上的注释。
Constant pool:
#1 = Methodref #4.#20 // java/lang/Object."<init>":()V
//(这是父类构造方法,声明构造方法的类描述符+ 名称 + 描述符)
#2 = Fieldref #3.#21 // com/gcb/jvm/bytecodetest/MyTest1.a:I
// (声明字段的的类描述符+ 名称 + 描述符)
#3 = Class #22 // com/gcb/jvm/bytecodetest/MyTest1
//(类或接口的全局限定名)
#4 = Class #23 // java/lang/Object
//(类或接口的全局限定名)
#5 = Utf8 a
#6 = Utf8 I
#7 = Utf8 <init>
#8 = Utf8 ()V // (方法的描述符,方法返回类型为void)
#9 = Utf8 Code
#10 = Utf8 LineNumberTable
#11 = Utf8 LocalVariableTable
#12 = Utf8 this
#13 = Utf8 Lcom/gcb/jvm/bytecodetest/MyTest1;
#14 = Utf8 getA // (方法名)
#15 = Utf8 ()I // (方法的描述符,方法返回类型为int)
#16 = Utf8 setA // (方法名)
#17 = Utf8 (I)V // (方法的描述符,方法参数为int,方法返回类型为void)
#18 = Utf8 SourceFile
#19 = Utf8 MyTest1.java // (18,19描述了文件是由什么文件编译出来的)
#20 = NameAndType #7:#8 // "<init>":()V
//(方法的名称 + 描述符,名称是"<init>",描述符是()V)
#21 = NameAndType #5:#6 // a:I
// (字段的名称 + 描述符,名称是a,描述符是I)
#22 = Utf8 com/gcb/jvm/bytecodetest/MyTest1 // (类完全限定名)
#23 = Utf8 java/lang/Object // (类完全限定名)
描述: U2类型,访问表示信息包括该Class文件是类还是接口,是否被定义为public ,是否是abstract ,如果是类,是否被声明为final。并且在字节码中,如果一个类是public 和 final ,它的字节码是 0001 + 0010 = 0011 。 通过上面的源代码,我们应该知道是类并且是public。
如上所示,类名,父类名,接口名都是U2类型两个字节,并且代表指向常量池的索引(下文说的索引一般也指指向常量池的索引)。如果接口数为零,则没有字节码是代表接口名的(感觉有点罗嗦了,哈哈哈,因为这很符合常识啊!)
域表用于描述类和接口中声明的变量。这里的字段包含了类级别的变量,以及实例变量,但是不包括方法内部声明的局部变量。关于域的描述如上所示,包括域的个数和域的表。其中域的表结构信息如下,表中的索引是指向常量池当中的。
关于方法的描述如上所示包括方法的个数和方法表,其中方法表的结构如下,表中的索引是指向常量池当中的。
方法表结构中的attributes 又是一个复合类型,attributes 的结构信息参考下面的附加属性表。 在JVM中预定了部分的attribute,但是编译器自己也可以实现自己的attribute写入class文件中,供运行时使用。不同的attribute通过attribute_name_index来区分。
attributes 中的其中一个attribute 叫做Code attribute ,是我们要重点研究的对象,它的作用是保存该方法的结构,它的结构如下(其实它的结构是attributes表的进一步展开时的结构)
Code attribute 的结构信息详解
关于附加属性表的描述如上所示,包括附加属性表的个数和附加属性表, 其中附加属性表的结构如下所示,表中的索引是指向常量池当中的。
newProxyInstance()方法的javadoc 文档
Returns an instance of a proxy class for the specified interfaces that dispatches method invocations to the specified invocation handler.
返回指定接口的代理类(proxy class)的实例,该接口将方法调用分派给指定的调用处理程序(invocation handler)。
@param
return 返回指定接口的代理类(proxy class)的实例,该接口将方法调用分派给指定的调用处理程序。
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。