在java之前,c/c++都是将程序编译成二进制本地机器码(汇编)然后交给操作系统去执行,但是这种方式的跨平台性能太差。而java在刚刚诞生之时就宣传的“一次编写,到处运行”就是为了解决这个跨平台的问题。而解决这个的关键就是class文件和java虚拟机。解决的方式是:将编写的程序编译(javac编译器等)成class文件-这种特定的二进制文件,java虚拟机就是可以执行这种特定二进制文件的虚拟机。
一.类文件结构
class文件里面是16进制表示的-class文件是特定的二进制文件,这是因为其实所有的文件都是二进制文件,因为计算机只能识别二进制,而class文件又是字节码文件,class文件的内容是16进制表示的,两个16进制数就是一个字节,class文件是以两位16进制数为最小单位来解析的。
图1.class文件的文件格式
下面是一个书上的例子,代码在书上166页。对这个例子进行解析。
class文件源码:
cafe babe 0000 0034 0016 0700 0201 001d
6f72 672f 6665 6e69 7873 6f66 742f 636c
617a 7a2f 5465 7374 436c 6173 7307 0004
0100 106a 6176 612f 6c61 6e67 2f4f 626a
6563 7401 0001 6d01 0001 4901 0006 3c69
6e69 743e 0100 0328 2956 0100 0443 6f64
650a 0003 000b 0c00 0700 0801 000f 4c69
6e65 4e75 6d62 6572 5461 626c 6501 0012
4c6f 6361 6c56 6172 6961 626c 6554 6162
6c65 0100 0474 6869 7301 001f 4c6f 7267
2f66 656e 6978 736f 6674 2f63 6c61 7a7a
2f54 6573 7443 6c61 7373 3b01 0003 696e
6301 0003 2829 4909 0001 0013 0c00 0500
0601 000a 536f 7572 6365 4669 6c65 0100
0e54 6573 7443 6c61 7373 2e6a 6176 6100
2100 0100 0300 0000 0100 0200 0500 0600
0000 0200 0100 0700 0800 0100 0900 0000
2f00 0100 0100 0000 052a b700 0ab1 0000
0002 000c 0000 0006 0001 0000 0003 000d
0000 000c 0001 0000 0005 000e 000f 0000
0001 0010 0011 0001 0009 0000 0031 0002
0001 0000 0007 2ab4 0012 0460 ac00 0000
0200 0c00 0000 0600 0100 0000 0800 0d00
0000 0c00 0100 0000 0700 0e00 0f00 0000
0100 1400 0000 0200 15
//////////////////////////////////////////////////////////////////////////
魔数:cafebabe
次版本号:0000
主版本号:0034
常量池容量计数值:0016(0016是这个类一共有21个常量)
第一个常量:
07(代表CONSTANT_Class_info类型,07是tag)
0002(0002是name_index,表示指向常量池中第二个常量)
第二个常量:
01(代表CONSTANT_Utf8_info类型,01是tag)
001d(代表这个Utf8字符串长度是29字节)
6f72 672f 6665 6e69 7873 6f66 742f 636c
617a 7a2f 5465 7374 436c 6173 73(这29个字节代表这个Utf8字符串的值:org/fenixsoft/clazz/TestClass)
(剩余的常量:07 0004
0100 106a 6176 612f 6c61 6e67 2f4f 626a
6563 7401 0001 6d01 0001 4901 0006 3c69
6e69 743e 0100 0328 2956 0100 0443 6f64
650a 0003 000b 0c00 0700 0801 000f 4c69
6e65 4e75 6d62 6572 5461 626c 6501 0012
4c6f 6361 6c56 6172 6961 626c 6554 6162
6c65 0100 0474 6869 7301 001f 4c6f 7267
2f66 656e 6978 736f 6674 2f63 6c61 7a7a
2f54 6573 7443 6c61 7373 3b01 0003 696e
6301 0003 2829 4909 0001 0013 0c00 0500
0601 000a 536f 7572 6365 4669 6c65 0100
0e54 6573 7443 6c61 7373 2e6a 6176 61)
访问标志:
0021(ACC_PUBLIC,ACC_SUPER)
类索引:
0001(指向常量池中索引为1的CONSTANT_Class_info类型,再由这个常量池索引为1的指向了索引为2的CONSTANT_Utf8_info类型的值,org/fenixsoft/clazz/TestClass)
父类索引:
0003(指向常量池中索引为3的CONSTANT_Class_info类型,再由这个常量池索引为3的指向了索引为4的CONSTANT_Utf8_info类型的值,java/lang/Object)
接口索引:
0000(该类没有实现接口,0000代表接口计数器,为0表示没有实现接口)
字段表集合:
0001(字段表入口,是一个容量计数器,这里0001表示这个类只有一个字段,这里的字段不包括方法中的局部字段)
0002(第一个字段修饰标志,0002表示该字段private修饰,其他都没有)
0005(第一个字段的简单名称name_index,这是对常量池常量的索引,0005表示常量池中第五个常量 m)
0006(第一个字段的描述符descriptor_index,这是对常量池常量的索引,0006表示常量池中第六个常量 I 也就是int类型)
0000(第一个字段的属性表,这里属性表为0000,就没有额外的属性值)
由上,可以得到这个类只有一个字段并且 字段是 private int m
方法表集合:
0002(方法表集合入口,是一个容量计数器,这里表示0002表示这个类只有两个方法)
0001(第一个方法的访问修饰标志,0001表示该方法public修饰,其他都没有)
0007(第一个方法的简单名称name_index,这是对常量池常量的索引,0007表示常量池中第七个常量 <init>这个方法是编译器添加的实例构造器)
0008(第一个方法的描述符descriptor_index,这是对常量池常量的索引,0008表示常量池中第八个常量 ()V 等价于void())
0001(第一个方法的属性表计数器,0001表示此方法的属性表集合有一项属性)
0009(第一个方法的第一个属性的attribute_name_index,是对常量池常量的索引,0009代表第九个常量 Code,Code是方法的字节码描述)
0000002f(第一个方法的第一个属性的attribute_length,是属性值的长度,0000002f代表该属性的长度是48)
0001(操作数栈的最大深度max_stack,0001代表栈帧中的操作栈深度最大为1)
0001(栈帧中局部变量表分配的位(slot)空间大小max_locals,其中除了double,long占2slot,其余都占1slt)
00000005(字节码指令长度code_length,00000005表示有5条字节码指令)
2a(第一个字节码指令(每个字节码指令都是一字节也就是两位16进制数)2a表示aload_0,将第一个引用类型本地变量推至栈顶)
b7(invokestatic 调用父类构造方法,实例初始化方法)
00(nop 什么都不做)
0a(lconst_1 将long型1推至栈顶)
b1(retrun 从当前方法返回void)
0000(第一个方法的显示异常处理表(代码中就类似try catch finally)exception_table_length,0000表示没有异常处理表)
0002(第一个方法的属性计数器attributes_count,这里0002表示有两个属性)
000c(第一个方法的第一个属性指向的常量池的索引attribute_name_index,000c表示指向常量池中第12个常量 LineNumberTable)
00000006(LineNumberTable的属性长度attribute_length)
0001(LineNumberTable的第一个属性line_number_table_length,0001表示line_number表的长度为1,有一个line_number_table)
0000 0003 (这个局部变量的生命开始start_pc和line_number,字节码行号和java源码行号)
000d(第一个方法的第二个属性指向的常量池的索引attribute_name_index,000d表示指向常量池中第13个常量 LocalVariableTable)
0000000c(LocalVariableTable的属性长度attribute_length)
0001(LocalVariableTable的局部变量表的长度local_variable_table_length,0001表示只有1个局部变量)
0000 0005(这个局部变量的生命的开始start_pc和作用范围length)
000e(这个局部变量指向的常量池中CONSTANT_Utf8_info类型的常量的名称索引name_index,000e表示指向 this)
000f(这个局部变量指向的常量池中CONSTANT_Utf8_info类型的常量的描述符索引descriptor_index,000f表示指向 Lorg/fenixsoft/clazz/TestClass(L表示对象类型))
0000(这个局部变量在栈帧局部变量表中的slot的位置,如果是double或long,那么占用的slot是index和index+1,这里0000表示占用的slot是第0个)
0001(第二个方法的访问修饰标志,0001表示该昂发public修饰,其他都没有)
0010(第二个方法的简单名称name_index,这是对常量池常量的索引,0010表示常量池中第16个常量 inc)
0011(第二个方法的描述符descriptor_index,这是对常量池常量的索引,0011表示常量池中第17个常量 ()I 等价于int())
0001(第二个方法的属性表计数器,0001表示此方法的属性表集合有一项属性)
0009(第二个方法的第一个属性的attribute_name_index,是对常量池常量的索引,0009代表第九个常量 Code,Code是方法的字节码描述)
00000031(第二个方法的第一个属性的attribute_length,是属性值的长度,0000031f代表该属性的长度是49)
0002(操作数栈的最大深度max_stack,0002代表栈帧中的操作栈深度最大为2)
0001(栈帧中局部变量表分配的位(slot)空间大小max_locals,其中除了double,long占2slot,其余都占1slt)
00000007(字节码指令长度code_length,00000007表示有7条字节码指令)
2a
b4
00
12
04
60
ac
0000(第二个方法的显示异常处理表(代码中就类似try catch finally)exception_table_length,0000表示没有异常处理表)
0002(第二个方法的属性计数器attributes_count,这里0002表示有两个属性)
000c(第二个方法的第一个属性指向的常量池的索引attribute_name_index,000c表示指向常量池中第12个常量 LineNumberTable)
00000006(LineNumberTable的属性长度attribute_length)
0001(LineNumberTable的第一个属性line_number_table_length,0001表示line_number表的长度为1,有一个line_number_table)
0000 0008(这个局部变量的生命开始start_pc和line_number,字节码行号和java源码行号)
000d(第二个方法的第二个属性指向的常量池的索引attribute_name_index,000d表示指向常量池中第13个常量 LocalVariableTable)
0000000cLocalVariableTable的属性长度attribute_length)
0001(LocalVariableTable的局部变量表的长度local_variable_table_length,0001表示只有1个局部变量)
0000 0007(这个局部变量的生命的开始start_pc和作用范围length)
000e(这个局部变量指向的常量池中CONSTANT_Utf8_info类型的常量的名称索引name_index,000e表示指向 this)
000f(这个局部变量指向的常量池中CONSTANT_Utf8_info类型的常量的描述符索引descriptor_index,000f表示指向 Lorg/fenixsoft/clazz/TestClass(L表示对象类型))
0000(这个局部变量在栈帧局部变量表中的slot的位置,如果是double或long,那么占用的slot是index和index+1,这里0000表示占用的slot是第0个)
0100 1400 0000 0200 15(sourcefile)
图2.javap生成的类文件解析