1、Java编译器将.java文件编译成为.class文件,实际上,是Java编译器读取源文件内容,经过一些列检查和分析后,整理成标准的、更方便Java虚拟机读取的字节码文件。

2、在官方jdk中默认的Java编译器是javac.exe,虚拟机是java.exe,java.exe实际上包含了真正的虚拟机HotSpot。

3、class文件是Java语言实现平台无关性、机器无关性和语言无关性的基石。

平台无关性:class文件内容屏蔽了关于平台的差异,即不管是windows系统、Linux系统、还是其它支持的系统,class文件都不会因此有差异。

机器无关性:class文件内容屏蔽了关于机器的差异,即不管是intel的x86指令架构或arm指令架构,不管是32位或者64位,class文件都不会因此有差异。

语言无关性:class文件内容屏蔽了关于语言的差异,不管是c语言、python语言等,借助对应的编译器,class文件都可以正常实现和解读。

类文件的结构

目前,我不关心Java编译器是如何将Java源代码编译成字节码文件的,只关心编译后的字节码的内容。

类型

名称

数量

u4

magic

魔数

1

u2

minor_version

次版本号

1

u2

major_version

主版本号

1

u2

constant_pool_count

常量池容量

1

cp_info

constan_pool

常量池

constant_pool_count

u2

access_flags

访问标志

1

u2

this_class

本类全限定名

1

u2

super_class

父类限定名

1

u2

interface_account

接口索引容量

1

u2

interfaces

接口索引

interface_account

u2

fields_count

字段表容量

1

field_info

fields

字段表

fields_count

u2

methods_count

方法表容量

1

method_info

methods

方法表

methods_count

u2

attributes_count

属性表容量

1

attribute_info

attributes

属性表

attributes_count

class文件是一组以8位字节为基础单位的二进制流,各个数据项目严格按照顺序紧凑地排列在Class文件之中,中间没有添加任何分隔符。

class文件中存储的数据类型分为两种:无符号数和表。

无符号数属于基本的数据类型,以u1、u2、u4、u8来分别代表1个字节、2字节、4个字节和8个字节的无符号数。

表由表项来组成,表项也可能是另一张表。

魔数

魔数是u4类型,即class文件的前4个字节的内容,作用是确定这个文件是否为一个被虚拟机接受的Class文件。魔数的默认值是0xCAFEBABE。

次版本号和主版本号

此版本号和主版本号都是u2类型,分别为class文件的第5、6个字节和第7、8个字节。

Java的版本号是从45开始的,即主版本号都在45及之上。高版本的JDK能向下兼容以前的Class文件,但不能运行以后版本的Class文件,即使文件格式并未发生任何变化,虚拟机也必须拒绝执行超过其版本号的Class文件。

常量池

紧接着主次版本号之后的是常量池入口,由于常量池中常量的数量是不固定的,所以在常量池的入口需要放置一项u2类型的数据,代表常量池容量计数值(constant_pool_count)。注意,该值是从1开始计数,当值为0x0016,即十进制为22,表示的范围是1~21,共21项常量。

常量池中主要存放两大类常量:字面量和符合引用。

常量池中每一项常量都是一个表,在JDK1.7及之后有14种常量池项目类型,每一种项目都有特定的表结构。

常量

描述

项目

类型

项目描述

CONSTANT_Utf8_info

UTF-8编码的字符串

tag

u1

值为1

length

u2

UTF-8编码的字符串占用的字节数

bytes

u1

长度为length的UTF-8编码的字符串

CONSTANT_Integer_info

整型字面量

tag

u1

值为3

bytes

u4

按照高位在前存储的int值

CONSTANT_Float_info

浮点型字面量

tag

u1

值为4

bytes

u4

按照高位在前存储的float值

CONSTANT_Long_info

长整型字面量

tag

u1

值为5

bytes

u8

按照高位在前存储的long值

CONSTANT_Double_info

双精度浮点型字面量

tag

u1

值为6

bytes

u8

按照高位在前存储的double值

CONSTANT_Class_info

类或接口的符合引用

tag

u1

值为7

index

u2

指向全限定名常量项的索引

CONSTANT_String_info

字符串类型字面量

tag

u1

值为8

index

u2

指向字符串字面量的索引

CONSTANT_Fieldref_info

字段的符号引用

tag

u1

值为9

index

u2

指向声明字段的类或者接口描述符CONSTANT_Class_info的索引项

index

u2

指向字段描述符CONSTANT_NameAndType的索引项

CONSTANT_Methodref_info

类中方法的符号引用

tag

u1

值为10

index

u2

指向声明方法的类描述符CONSTANT_Class_info的索引项

index

u2

指向名称及类型描述符CONSTANT_NameAndType的索引项

CONSTANT_InterfaceMethodref_info

接口中方法的符号引用

tag

u1

值为11

index

u2

指向声明方法的接口描述符CONSTANT_Class_info的索引项

index

u2

指向名称及类型描述符CONSTANT_NameAndType的索引项

CONSTANT_NameAndType_info

字段或方法的部分符号引用

tag

u1

值为12

index

u2

指向该字段或方法名称常量项的索引

index

u2

指向该字段或方法描述符常量项的索引

CONSTANT_MethodHandle_info

表示方法句柄

tag

u1

值为15

reference_kind

u1

值必须在1~9范围,它决定了方法句柄的类型。方法句柄类型的值表示方法句柄的字节码行为

reference_index

u2

值必须是对常量池的有效索引

CONSTANT_MethodType_info

标识方法类型

tag

u1

值为16

descriptor_index

u2

值必须是对常量池的有效索引,常量池在该索引处的项必须是CONSTANT_Utf8_info结构,表示方法的描述符

CONSTANT_InvokeDynamic_info

表示一个动态方法调用点

tag

u1

值为18

bootstrap_method_attr_index

u2

值必须是对当前Class文件中引导方法表的bootstrap_methods[]数组的的有效索引

name_and_type_index

u2

值必须是对当前常量池的有效索引,常量池在该索引处的项必须是CONSTANT_NameAndType_info结构,表示方法名和方法描述符

访问标志

在常量池结束之后,紧接着的两个字节代表访问标志,这个标志用于识别 一些类或者接口层次的访问信息。

标志名称

标志值

含义

ACC_PUBLIC

0x0001

是否为public类型

ACC_FINAL

0x0010

是否被声明为final类型,只有类可设置

ACC_SUPER

0x0020

是否允许使用invokespecial字节码指令的新语意,invokespecial指令的语意在JDB1.2之后发生过改变,为了区别这条指令使用哪种语意,JDK1.0.2之后编译出来的类的这个标志都必须为真。

ACC_INTERFACE

0x0200

标识这是一个接口

ACC_ABSTRACT

0x0400

是否为abstract类型,对于接口或者抽象类来说,此标志为真,其它类值为假

ACC_SYNTHETIC

0x1000

标识这个类并非由用户代码产生的

ACC_ANNOTATION

0x2000

标识这是一个注解

ACC_ENUM

0x4000

标识这是一个枚举

类索引、父类索引和接口索引集合

类索引、父类索引和接口索引集合都按顺序排列在访问标志之后,类索引和父类索引用两个u2类型的索引值表示,它们各自指向一个类型为CONSTANT_Class_info的类描述符常量,通过CONSTANT_Class_info类型的常量中的索引值找到可以定义在CONSTANT_Utf8_info类型的常量中的全限定名字符串。

对于接口索引集合,入口的第一项——u2类型的数据为接口计数器,表示索引表的容量。如果该类没有实现任何接口,则该计数器值为0.后面的索引表不再占用任何字节。

字段表集合

在字段表集合的开头是一个u2类型的数据fields_count,表示字段表的个数。

字段表用于描述接口或者类中声明的变量。字段包括类级变量和实例级变量,但不包括声明在方法内部声明的局部变量。

字段表结构:

类型

名称

数量

u2

access_flags

1

u2

name_index

1

u2

descriptor_index

1

u2

attributes_count

1

attribute_info

attributes

attributes_count

字段访问标志:

标志名称

标志值

含义

ACC_PUBLIC

0x0001

字段是否为public

ACC_PRIVATE

0x0002

字段是否为private

ACC_PROTECTED

0x0004

字段是否为protected

ACC_STATIC

0x0008

字段是否为static

ACC_FINAL

0x0010

字段是否为final

ACC_VOLATILE

0x0040

字段是否为volatile

ACC_TRANSIENT

0x0080

字段是否为transient

ACC_SYNTHETIC

0x1000

字段是否由编译器自动产生的

ACC_ENUM

0x4000

字段是否enum

name_index和descriptor_index都是对常量池的引用,分别代表着字段的简单名称以及字段和方法的描述符。

 

方法表集合

在方法表集合的开头是一个u2类型的数据methods_count,表示方法表的个数。

方法表结构与字段表结构一致:

类型

名称

数量

u2

access_flags

1

u2

name_index

1

u2

descriptor_index

1

u2

attributes_count

1

attribute_info

attributes

attributes_count

访问标志与字段表的访问标志也很相似:区别在于volatile和transient不能修饰方法,但增加了synchronized、native、strictfp和abstract关键字。

标志名称

标志值

含义

ACC_PUBLIC

0x0001

方法是否为public

ACC_PRIVATE

0x0002

方法是否为private

ACC_PROTECTED

0x0004

方法是否为protected

ACC_STATIC

0x0008

方法是否为static

ACC_FINAL

0x0010

方法是否为final

ACC_SYNCHRONIZED

0x0020

方法是否为synchronized 

ACC_BRIDGE

0x0040 

方法是否是由编译器产生的桥接方法 

ACC_VARARGS

0x0080 

方法是否接受不定参数 

ACC_NATIVE

0x0100 

方法是否为native 

ACC_ABSTRACT

0x0400 

方法是否为abstract 

ACC_STRICTFP

0x0800 

方法是否为strictfp 

ACC_SYNTHETIC 

0x1000 

方法是否是由编译器自动产生的 

 方法中的Java代码,经过编译器编译成字节码后,存放在方法属性表集合中一个名为“Code”的属性里面。

属性表集合

 在方法表集合的开头是一个u2类型的数据attributes_count,表示属性表的个数。

对于每个属性,它的名称需要从常量池中引用一个CONSTANT_Utf8_info类型的常量来表示,而属性值的结构则是完全自定义的,只需要通过一个u4的长度去说明属性值所占用的位数即可。

属性表结构:

类型

名称

数量

u2

attribute_name_index

1

u4

attribute_length

1

u1

info

attribute_length

1、Code属性

类型

名称

数量

u2

attribute_name_index

1

u4

attribute_length

1

u2

max_stack

1

u2

max_local

1

u4

code_length

1

u1

code

code_length

u2

exception_table_length

1

exception_info

exception_table

exception_table_length

u2

attributes_count

1

attribute_info

attributes

attributes_count