1、methods:指向常量池索引集合,它完整描述了每个方法的签名。在字节码文件中,每一个method_info项都对应着一个类或者接口中的方法信息。比如方法的访问修饰符(public、private或protected),方法的返回值类型以及方法的参数信息等。 2、methods表只描述当前类或接口中声明的方,不包括从父类或父接口继承的方法。 3、methods表有可能会出现由编译器自动添加的方法,最典型的便是编译器产生的方法信息(比如:类(接口)初始化方法2、方法计数器() 和实例初始化方法 ()) 4、补充说明:
- 在Java语言中,要重载(Overload)一个方法,除了要与原方法具有相同的简单名称之外,还要求必须拥有一个与原方法不同的特征签名。
- 特征签名就是一个方法中各个参数在常量池中的字段符号引用的集合,也就是因为返回值不会包含在特征签名之中,因此Java语言里无法仅仅依靠返回值的不同来对一个已有方法进行重载。但在Class文件格式中,特征签名的范围更大一些,只要描述符不是完全一致的两个方法就可以共存。也就是说,如果两个方法有相同的名称和特征签名,但返回值不同,那么也是可以合法共存于同一个Class文件中。
- 也就是说,尽管Java语法规范并不允许在一个类或者接口中声明多个方法签名相同的方法,但是和Java语法规范相反,字节码文件中却恰恰允许存放多个方法签名相同的方法,唯一的条件就是这些方法之间的返回值不能相同。
1、方法计数器(methods_count):它的值表示当前Class文件methods表的成员个数,使用两个字节来表示。示例代码中方法计数器值为0x0002,表示有两个方法。3、方法表结构
1、methods表中的每个成员都必须是一个method_info结构,用于表示当前类或接口中某个方法的完整描述。如果某个method_info结构的access_flags项既没有设置ACC_NATIVE标志也没有设置ACC_ABSTRACT标志,那么该结构中也应包含实现这个方法所有的Java虚拟机指令。 2、method_info结构可以表示类和接口中定义的所有方法,包括实例方法、类方法、实例初始化方法和类或接口初始化方法。 3、方法表的结构如同字段表一样,依次包括访问标志(access_flags)、名称索引(name_index)、描述符索引(descriptor_index)、属性表集合(attributes)几项。如下图所示:4、方法表数据解读4、方法表访问标志
- 由于volatile关键字和transient关键字不能修饰方法,因此方法表的访问标志中没有了ACC_VOLATILE标志和ACC_TRANSIENT标志。
- 由于synchronized、native、strictfp和abstract关键字可以修饰方法,因此方法表的访问标志中也相应地增加了ACC_SYNCHRONIZED、 ACC_NATIVE、ACC_STRICTFP和ACC_ABSTRACT标志。
1、第一个是u2类型的数据(即方法计数器)的值为0x0002,代表方法集合中有两个方法。两个方法的访问标志都为0x0001,通过查表发现ACC_PUBLIC标志为真,表示两个方法都为public。二、Class文件结构之属性表集合 1、概述2、第一个方法的方法名索引值为0x0007,查询常量池中的指定索引项即可知道方法的名称为
,方法描述符的索引值为0x0008,查询常量池中的指定索引项即可知道方法的返回值类型V和入参(), 3、第二个方法的方法名索引值为0x000e,查询常量池中的指定索引项即可知道方法的名称为add,方法描述符的索引值为0x000f,查询常量池中的指定索引项即可知道方法的返回值类型V和入参(),
4、两个方法的属性计数器值为0x0001,表示此方法的属性表集合有1项属性,属性名称的索引值为0x0009,对应的常量为Code,说明此属性是方法的字节码描述。
1、方法表集合之后是属性表集合,表示Class文件所携带的辅助信息。比如该Class文件的源文件的名称,以及任何带有RetentionPolicy.CLASS或者RetentionPolicy.RUNTIME的注解。 2、Class文件、字段表、方法表都可以有自己的属性表,用于描述某些场景专有的信息。 3、与Class文件中其他的数据项目要求严格的顺序、长度和内容不同,属性表集合的限制稍微宽松一 些,不再要求各个属性表具有严格顺序,并且《Java虚拟机规范》允许只要不与已有属性名重复,任何人实现的编译器都可以向属性表中写入自己定义的属性信息,但Java虚拟机运行时会忽略掉它不认识的属性。2、属性计数器
属性计数器(attributes_count):它的值表示当前Class文件属性表的成员个数,使用两个字节表示。属性表中每一项都是一个attribute_info结构。3、属性类型与属性表结构
1、属性类型:属性表实际上有很多类型,上面的方法表中看到的Code属性只是其中一种。三、属性表中Code属性 1、概述
2、属性表结构:属性表的每个项的值必须是attribute_info结构。属性表的结构比较灵活,对于每一个属性,它的名称都要从常量池中引用一个CONSTANT_Utf8_info类型的常量来表示, 而属性值的结构则是完全自定义的,只需要通过一个u4的长度属性去说明属性值所占用的位数即可。
1、Java程序方法体里面的代码经过Javac编译器处理之后,最终变为字节码指令存储在Code属性内。Code属性出现在方法表的属性集合之中,但并非所有的方法表都必须存在这个属性,譬如接口或者抽象类中的方法(因为没有方法体)就不存在Code属性。如果方法表有Code属性存在,那么它的结构如下图,Code属性表的前两项跟属性表是一致的,即Code属性表遵循属性表的结构,后面那些则是他自定义的结构。2、方法表中的Code属性解读2、attribute_name_index是一项指向CONSTANT_Utf8_info型常量的索引,此常量值固定为Code,它 代表了该属性的属性名称,attribute_length指示了属性值的长度,由于属性名称索引与属性长度一共为6个字节,所以属性值的长度固定为整个属性表长度减去6个字节。 3、max_stack代表了操作数栈(Operand Stack)深度的最大值。在方法执行的任意时刻,操作数栈都不会超过这个深度。虚拟机运行的时候需要根据这个值来分配栈帧(Stack frame)中的操作栈深度。 3、max_locals代表了局部变量表所需的存储空间。
4、code_length和code用来存储Java源程序编译后生成的字节码指令。
- max_locals的单位是变量槽(Slot),变量槽是虚拟机为局部变量分配内存所使用的最小单位。
- 对于byte、char、float、int、short、boolean和returnAddress等长度不超过32位的数据类型,每个局部变量占用一个变量槽,而double和long这两种64位的数据类型则需要两个变量槽来存放。
- 方法参数(包括实例方法中的隐藏参数“this”)、显式异常处理程序的参数(Exception Handler Parameter,就是try-catch语句中catch块中所定义的异常)、方法体中定义的局部变量都需要依赖局部变量表来存放。
- 并不是在方法中用了多少个局部变量,就把这些局部变量所占变量槽数量之和作为max_locals的值,操作数栈和局部变量表直接决定一个该方法的栈帧所耗费的内存,不必要的操作数栈深度和变量槽数量会造成内存的浪费。
- Java虚拟机的做法是将局部变量表中的变量槽进行重用,当代码执行超出一个局部变量的作用域时,这个局部变量所占的变量槽可以被其他局部变量所使用,Javac编译器会根据变量的作用域来分配变量槽给各个变量使用,根据同时生存的最大局部变量数量和类型计算出max_locals的大小。
- code_length代表字节码长度, code是用于存储字节码指令的一系列字节流。
- 字节码指令顾名思义每个指令就是一个u1类型的单字节,当虚拟机读取到code中的一个字节码时,就可以对应找出这个字节码代表的是什么指令,并且可以知道这条指令后面是否需要跟随参数,以及后续的参数应当如何解析。
- 一个u1数据类型的取值范围为0x00~0xFF,对应十进制的0~255,也就是一共可以表达256条指令。
- 对于code_length虽然它是一个u4类型的长度值,理论上最大值可以达到2的32次幂,但是《Java虚拟机规范》中明确限制了一个方法不允许超过65535条字节码指令,即它实际只使用了u2的长度,如果超过这个限制,Javac编译器就会拒绝编译。
1、以示例代码为例,它的第一个方法是四、属性表中LineNumberTable属性 1、概述()方法,在属性名索引之后是一个u4类型的属性长度(0x00000038),对应十进制是56,说明Code属性有56个字节长度;操作数栈的最大深度为0x0002,对应十进制是2;局部变量表的容量为0x0001,对应十进制是1;字节码指令的长度为0x0000000a,对应十进制为10,说明字节码指令有10个字节长度;异常表的长度为0x0000,对应十进制为0,说明没有任何异常的,因此也不存在异常表结构;Code属性的属性计数器为0x0002,对应十进制为2,说明Code属性中还有2个属性。 2、通过jclasslib工具查看到的init方法的具体的字节码指令如下:
3、具体的字节码指令中第一个是0x2a,通过《官方文档——虚拟机指令集》中查找到对应的指令为aload_0,这个指令的含义是将第0个变量槽中为reference类型的本地变量推送到操作数栈顶。 4、第二个字节码指令为b7,通过查文档得到对应的指令为invokespecial,这个指令的作用是以栈顶的reference类型的数据所指向的对象作为方法的接收者,调用此对象的构造器方法、private方法或者它的父类的方法。这个方法中有一个u2类型的参数说明具体调用哪一个方法,它指向常量池中的一个CONSTANT_Methodref_info类型常量,即此方法的符号引用。 5、紧接着是0x2a,对应的指令为aload_0;然后是0x04,对应的指令为iconst_1,这个指令的含义是将int常量压入操作数栈。然后是0xb5,对应的指令为putfield,含义是设置对象中字段的值,它下面有一个u2类型的参数说明具体是哪一个字段,指向常量池中的一个CONSTANT_Fieldref_info类型常量,即字段的符号引用。紧接着是0xb1,对应的指令为return,含义是从方法的返回,并且返回值为void。这条指令执行后,当前方法正常结束。
1、LineNumberTable属性用于描述Java源码行号与字节码行号(字节码的偏移量)之间的对应关系。 它并不是运行时必需的属性,但默认会生成到Class文件之中,可以在Javac中使用-g:none或-g:lines选项来取消或要求生成这项信息。如果选择不生成LineNumberTable属性,对程序运行产生的最主要影 响就是当抛出异常时,堆栈中将不会显示出错的行号,并且在调试程序的时候,也无法按照源码行来设置断点。 2、line_number_table是一个数量为line_number_table_length、类型为line_number_info的集合, line_number_info表包含start_pc和line_number两个u2类型的数据项,前者是字节码行号,后者是Java源码行号。属性结构如下:2、Code属性中的LineNumberTable解读
1、Code属性的属性计数器为0x0002,说明它还有两个属性,第一个属性名的索引值为0x000a,查询常量池中的指定索引项即可知道属性的名称为LineNumberTable。属性长度值为0x0000000a,说明属性占用10个字节。line_number_table的长度为0x0002,说明有2个line_number_info。五、属性表中LocalVariableTable属性 1、概述
2、line_number_info结构中分别为两个start_pc和line_number,对应的字节码行号分别为0x0000与0x0004,对应的源码行号分别为0x0007与0x0008。通过jclasslib工具也可以查看到对应的内容
1、LocalVariableTable属性用于描述栈帧中局部变量表的变量与Java源码中定义的变量之间的关系,它也不是运行时必需的属性,但默认会生成到Class文件之中,可以在Javac中使用-g:none或-g:vars选项来取消或要求生成这项信息。如果没有生成这项属性,最大的影响就是当其他人引用这个方法时,所 有的参数名称都将会丢失,譬如IDE将会使用诸如arg0、arg1之类的占位符代替原有的参数名,这对程 序运行没有影响,但是会对代码编写带来较大不便,而且在调试期间无法根据参数名称从上下文中获得参数值。属性结构如下2、Code属性中的LocalVariableTable解读2、LocalVariableTable属性可以按照任意顺序出现。Code属性中的每个局部变量最多只能有一个该属性。 3、start_pc+length表示这个变量在字节码中的生命周期起始和结束的偏移位置 4、index 就是这个变量在局部变量表中的槽位(槽位可复用),当这个变量数据类型是64位类型时 (double和long),它占用的变量槽为index和index+1两个。
1、第二个属性名的索引值为0x000b,查询常量池中的指定索引项即可知道属性的名称为LocalVariableTable。属性长度值为0x0000000c,说明属性占用12个字节。local_variable_table的长度为0x0001,说明有1个local_variable_info。六、属性表中SourceFile属性 1、概述
2、local_variable_info结构中,生命周期开始的字节码偏移量(start_pc)值为0x0000。作用范围覆盖的长度(length)值为0x000a,说明this生命周期从0到结尾10。局部变量的名称索引(name_index)值为0x000c,对应的名称为this。局部变量的描述符索引(descriptor_index)值为0x000d,对应描述符为Lcom/itan/middle/day1/Demo;。局部变量在变量槽的位置(index)值为0x0000,对应在变量槽的索引为0位置上。通过jclasslib工具也可以查看到对应的内容。
3、示例代码的第二个方法解读步骤和第一个方法解读步骤一样,最后整个解读完成之后如下图所示
1、SourceFile属性用于记录生成这个Class文件的源码文件名称。这个属性也是可选的,可以使用Javac的-g:none或-g:source选项来关闭或要求生成这项信息。在Java中,对于大多数的类来说,类名和文 件名是一致的,但是有一些特殊情况(如内部类)例外。如果不生成这项属性,当抛出异常时,堆栈 中将不会显示出错代码所属的文件名。这个属性是一个定长的属性。属性结构如下
2、sourcefile_index数据项是指向常量池中CONSTANT_Utf8_info型常量的索引,常量值是源码文件的文件名。2、SourceFile属性解读
1、在方法表中Code属性之后,就是其他的一些附加属性,首先是一个u2类型的属性计数器的值为0x0001,说明一个有一个属性,属性名索引值为0x0010,查询常量池中的指定索引项即可知道属性的名称为SourceFile。属性长度值为0x00000002,源码文件索引值为0x0011,查询常量池中的指定索引项即可知道对应的文件名为Demo.java。七、属性表中ConstantValue与InnerClasses属性 1、ConstantValue属性概述与结构
2、到此示例代码全部解读完成,其他的属性,参考官网,按照上述解读方式进行解读。
1、ConstantValue属性的作用是通知虚拟机自动为静态变量赋值。只有被static关键字修饰的变量(类变量)才可以使用这项属性。2、InnerClasses属性概述与结构2、属性结构
- 类似“int x=123”和“static int x=123”这样的变量定义在Java程序里面是非常常见的事情,但虚拟机对这两种变量赋值的方式和时刻都有所不同。
- 对非static类型的变量(也就是实例变量)的赋值是在实例构造器
()方法中进行的; - 对于类变量,则有两种方式可以选择:在类构造器
()方法中或者使用ConstantValue属性。 - 目前Oracle公司实现的Javac编译器的选择是,如果同时使用final和static来修饰一个变量(按照习惯,这里称“常量”更贴切),并且这个变量的数据类型是基本类型或者java.lang.String的话,就将会生成ConstantValue属性来进行初始化;如果这个变量没有被final修饰,或者并非基本类型及字符串,则将会选择在
()方法中进行初始化。
1、InnerClasses属性用于记录内部类与宿主类之间的关联。如果一个类中定义了内部类,那编译器将会为它以及它所包含的内部类生成InnerClasses属性。属性结构如下:八、使用javap指令解析Class文件 1、解析字节码的作用2、内部类的访问标志
1、通过反编译生成的字节码文件,可以深入了解Java代码的工作机制。但是,自己分析类文件结构太麻烦了,除了使用第三方的jclasslib工具之外,Oracle官方也提供了javap工具。 2、javap是JDK自带的反解析工具。它的作用就是根据Class字节码文件,反解析出当前类对应的Code区(字节码指令)、局部变量表、异常表和代码行偏移量映射表、常量池等信息。 3、通过局部变量表,可以查看局部变量的作用域范围、所在槽位等信息,甚至可以看到槽位复用等信息。2、javac -g操作
1、解析字节码文件得到的信息中,有些信息(如局部变量表、指令和代码行偏移量映射表、常量池中方法的参数名称等等)需要在使用javac编译成Class文件时,指定参数才能输出。 2、如果直接使用javac xx.java,就不会在生成对应的局部变量表等信息,如果使用javac -g xx.java就可以生成所有相关信息了。 3、如果使用的Eclipse或IDEA,则默认情况下,Eclipse、IDEA在编译时会帮你生成局部变量表、指令和代码行偏移量映射表等信息。3、javap用法
1、javap使用格式:javap4、解析javap命令得到的文件结构2、查看有哪些可选参数options方式
- options:可选参数
- classes:要反编译的Class文件
- javap命令
- javap -help命令
3、补充说明
- -version:版本信息,其实是当前javap所在jdk的版本信息,不是class在哪个jdk生成的
- -v -verbose:输出附加信息(包括行号、本地变量表,反汇编等详细信息)
- javap -c:会对当前Class 字节码进行反编译生成汇编代码。
- javap -v xxx.class:除了包含-c内容外,还会输出行号、局部变量表信息、常量池等信息。
- 由于-v是不包含私有的,如果想反编译时带上私有的结构,指令加上-p,javap -v -p xxx.class
public class JavapTest {
private int num;
boolean flag;
protected char sex;
public String info;
public static final int COUNTS = 1;
static {
String url = "http:localhost:80";
}
{
info = "java";
}
public JavapTest() {
}
public JavapTest(boolean flag) {
this.flag = flag;
}
private void method1(){
}
int getNum(int i) {
return num + i;
}
protected char showSex() {
return sex;
}
public void getInfo() {
int i = 10;
System.out.println(info + i);
}
}
Classfile /C:/Users/安然/Desktop/JavapTest.class //字节码文件所属的路径 Last modified 2021-11-21; size 1353 bytes //最后修改时间,字节码文件的大小 MD5 checksum 31a0f9ebe848d30cb4ed41e3411dcf2d //MD5散列值 Compiled from "JavapTest.java" //源文件名称 public class com.itan.middle.day1.JavapTest //类的权限定名 minor version: 0 //副版本 major version: 52 //主版本 flags: ACC_PUBLIC, ACC_SUPER //类的访问标志 Constant pool: //常量池信息 #1 = Methodref #16.#46 // java/lang/Object."5、总结":()V #2 = String #47 // java #3 = Fieldref #15.#48 // com/itan/middle/day1/JavapTest.info:Ljava/lang/String; #4 = Fieldref #15.#49 // com/itan/middle/day1/JavapTest.flag:Z #5 = Fieldref #15.#50 // com/itan/middle/day1/JavapTest.num:I #6 = Fieldref #15.#51 // com/itan/middle/day1/JavapTest.sex:C #7 = Fieldref #52.#53 // java/lang/System.out:Ljava/io/PrintStream; #8 = Class #54 // java/lang/StringBuilder #9 = Methodref #8.#46 // java/lang/StringBuilder." ":()V #10 = Methodref #8.#55 // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; #11 = Methodref #8.#56 // java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder; #12 = Methodref #8.#57 // java/lang/StringBuilder.toString:()Ljava/lang/String; #13 = Methodref #58.#59 // java/io/PrintStream.println:(Ljava/lang/String;)V #14 = String #60 // http:localhost:80 #15 = Class #61 // com/itan/middle/day1/JavapTest #16 = Class #62 // java/lang/Object #17 = Utf8 num #18 = Utf8 I #19 = Utf8 flag #20 = Utf8 Z #21 = Utf8 sex #22 = Utf8 C #23 = Utf8 info #24 = Utf8 Ljava/lang/String; #25 = Utf8 COUNTS #26 = Utf8 ConstantValue #27 = Integer 1 #28 = Utf8 #29 = Utf8 ()V #30 = Utf8 Code #31 = Utf8 LineNumberTable #32 = Utf8 LocalVariableTable #33 = Utf8 this #34 = Utf8 Lcom/itan/middle/day1/JavapTest; #35 = Utf8 (Z)V #36 = Utf8 method1 #37 = Utf8 getNum #38 = Utf8 (I)I #39 = Utf8 i #40 = Utf8 showSex #41 = Utf8 ()C #42 = Utf8 getInfo #43 = Utf8 #44 = Utf8 SourceFile #45 = Utf8 JavapTest.java #46 = NameAndType #28:#29 // " ":()V #47 = Utf8 java #48 = NameAndType #23:#24 // info:Ljava/lang/String; #49 = NameAndType #19:#20 // flag:Z #50 = NameAndType #17:#18 // num:I #51 = NameAndType #21:#22 // sex:C #52 = Class #63 // java/lang/System #53 = NameAndType #64:#65 // out:Ljava/io/PrintStream; #54 = Utf8 java/lang/StringBuilder #55 = NameAndType #66:#67 // append:(Ljava/lang/String;)Ljava/lang/StringBuilder; #56 = NameAndType #66:#68 // append:(I)Ljava/lang/StringBuilder; #57 = NameAndType #69:#70 // toString:()Ljava/lang/String; #58 = Class #71 // java/io/PrintStream #59 = NameAndType #72:#73 // println:(Ljava/lang/String;)V #60 = Utf8 http:localhost:80 #61 = Utf8 com/itan/middle/day1/JavapTest #62 = Utf8 java/lang/Object #63 = Utf8 java/lang/System #64 = Utf8 out #65 = Utf8 Ljava/io/PrintStream; #66 = Utf8 append #67 = Utf8 (Ljava/lang/String;)Ljava/lang/StringBuilder; #68 = Utf8 (I)Ljava/lang/StringBuilder; #69 = Utf8 toString #70 = Utf8 ()Ljava/lang/String; #71 = Utf8 java/io/PrintStream #72 = Utf8 println #73 = Utf8 (Ljava/lang/String;)V ##########################################################字段表集合的信息########################################################## { private int num; //字段名 descriptor: I //字段描述符:字段的类型 flags: ACC_PRIVATE //字段的访问权限 boolean flag; descriptor: Z flags: protected char sex; descriptor: C flags: ACC_PROTECTED public java.lang.String info; descriptor: Ljava/lang/String; flags: ACC_PUBLIC public static final int COUNTS; descriptor: I flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL ConstantValue: int 1 //常量字段的属性:ConstantValue ##########################################################方法表集合的信息########################################################## public com.itan.middle.day1.JavapTest(); //构造器的信息 descriptor: ()V flags: ACC_PUBLIC Code: stack=2, locals=1, args_size=1 0: aload_0 1: invokespecial #1 // Method java/lang/Object." ":()V 4: aload_0 5: ldc #2 // String java 7: putfield #3 // Field info:Ljava/lang/String; 10: return LineNumberTable: line 22: 0 line 19: 4 line 23: 10 LocalVariableTable: Start Length Slot Name Signature 0 11 0 this Lcom/itan/middle/day1/JavapTest; public com.itan.middle.day1.JavapTest(boolean); //构造器的信息 descriptor: (Z)V flags: ACC_PUBLIC Code: stack=2, locals=2, args_size=2 0: aload_0 1: invokespecial #1 // Method java/lang/Object." ":()V 4: aload_0 5: ldc #2 // String java 7: putfield #3 // Field info:Ljava/lang/String; 10: aload_0 11: iload_1 12: putfield #4 // Field flag:Z 15: return LineNumberTable: line 25: 0 line 19: 4 line 26: 10 line 27: 15 LocalVariableTable: Start Length Slot Name Signature 0 16 0 this Lcom/itan/middle/day1/JavapTest; 0 16 1 flag Z private void method1(); descriptor: ()V flags: ACC_PRIVATE Code: stack=0, locals=1, args_size=1 0: return LineNumberTable: line 31: 0 LocalVariableTable: Start Length Slot Name Signature 0 1 0 this Lcom/itan/middle/day1/JavapTest; int getNum(int); descriptor: (I)I flags: Code: stack=2, locals=2, args_size=2 0: aload_0 1: getfield #5 // Field num:I 4: iload_1 5: iadd 6: ireturn LineNumberTable: line 34: 0 LocalVariableTable: Start Length Slot Name Signature 0 7 0 this Lcom/itan/middle/day1/JavapTest; 0 7 1 i I protected char showSex(); descriptor: ()C flags: ACC_PROTECTED Code: stack=1, locals=1, args_size=1 0: aload_0 1: getfield #6 // Field sex:C 4: ireturn LineNumberTable: line 38: 0 LocalVariableTable: Start Length Slot Name Signature 0 5 0 this Lcom/itan/middle/day1/JavapTest; public void getInfo(); descriptor: ()V //方法描述符(形参列表+返回值类型) flags: ACC_PUBLIC //方法访问标志 Code: //方法的Code属性 stack=3, locals=2, args_size=1 //stack:操作数栈的最大深度;locals:局部变量表的长度;args_size:方法接受参数的个数 //偏移量 字节码指令 操作数 0: bipush 10 2: istore_1 3: getstatic #7 // Field java/lang/System.out:Ljava/io/PrintStream; 6: new #8 // class java/lang/StringBuilder 9: dup 10: invokespecial #9 // Method java/lang/StringBuilder." ":()V 13: aload_0 14: getfield #3 // Field info:Ljava/lang/String; 17: invokevirtual #10 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 20: iload_1 21: invokevirtual #11 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder; 24: invokevirtual #12 // Method java/lang/StringBuilder.toString:()Ljava/lang/String; 27: invokevirtual #13 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 30: return //行号表:指明字节码指令的偏移量与java源程序中代码的行号的一一对应关系 LineNumberTable: line 42: 0 line 43: 3 line 44: 30 //局部变量表:描述方法内部局部变量的相关信息 LocalVariableTable: Start Length Slot Name Signature 0 31 0 this Lcom/itan/middle/day1/JavapTest; 3 28 1 i I static {}; descriptor: ()V flags: ACC_STATIC Code: stack=1, locals=1, args_size=0 0: ldc #14 // String http:localhost:80 2: astore_0 3: return LineNumberTable: line 15: 0 line 16: 3 LocalVariableTable: Start Length Slot Name Signature } SourceFile: "JavapTest.java" //附加属性:指明当前字节码文件对应的源程序文件名
1、通过javap命令可以查看一个Java类解析得到的Class文件版本号、常量池、访问标识、变量表、指令代码行号表等信息。没有显示类索引、父类索引、接口索引集合、clinit、init等结构。但是反编译之后会将clinit还原成静态代码,init还原成构造方法。 2.通过对示例代码解析文件的简单分析,可以发现,一个方法的执行通常会涉及下面几块内存的操作
- Java栈中:局部变量表、操作数栈
- Java堆: 通过对象的地址引用去操作
- 常量池
- 其他如帧数据区、方法区的剩余部分等情况,测试中没有显示出来。



