要想深刻的了解jvm,了解java编译后的类文件结构和字节码是颇有必要的。虽然这部份内容(主要是class文件的数据结构)比较枯燥,可是这也是最基础的内容,是咱们深刻理解jvm的内存、类的加载等内容的基石。java
class文件是一组以8位字节为基础的二进制流,各个数据项目按照顺序排列在Class文件中,中间没有任何分隔符。所以整个class文件中存储的内容几乎全是程序运行时的必要数据。当遇到须要占用8位以上字节空间的数据项时,会按照高位在前的方式分割成若干个8位字节存储。数组
class文件格式以下:数据结构
类型 | 名称 | 数量 |
---|---|---|
u4 | magic | 1 |
u2 | minor_version | 1 |
u2 | major_version | 1 |
u2 | constant_pool_count | 1 |
cp_info | constant_pool | constant_pool_count - 1 |
u2 | access_flags | 1 |
u2 | this_class | 1 |
u2 | super_class | 1 |
u2 | interfaces_count | 1 |
u2 | interfaces | interfaces_count |
u2 | fields_count | 1 |
field_info | fields | fields_count |
u2 | methods_count | 1 |
method_info | methods | methods_count |
u2 | attribute_count | 1 |
attribute_info | attributes | attributes_count |
class文件只有两种伪数据结构:无符号数和表。能够看到每一个表的前面都会有一XX count的,这是一个前置容量计数器,用来记录对应类型的数量。并发
class文件的数据项不少,这里不展开一个个地讲,主要介绍一些关键的。jvm
- 常量池
- 字段表
- 方法表
- 属性表
常量池相信不少人都听过,这里的常量池指的是class文件中的常量池。布局
常量池中主要存储两类类型:字面量和符号引用。this
字面量:字面量指的是java语言层的常亮,如String s="123",那么这个"123"就是常量。对于基本类型的封装类型,范围在-127-128之间的,也是常量。固然了,声明为final的值,在整个程序运行过程当中不可变,天然也是常量了。编码
符号引用:java中的符号引用主要包括如下3类常量:翻译
java是在虚拟机加载class文件的时候进行动态链接的,class文件中不会保存各方法字段的最终内存布局信息,所以这些字段、方法的符号引用不通过虚拟机运行时转换的话,没法获得真正的内存地址,也就没法被jvm使用。当虚拟机运行时,须要从常量池得到对应的符号引用,再在类建立或运行时解析,翻译到具体的内存地址中。调试
常量池中的每一项常量都是一个表,这些表有一个共同的特色,每一个表的第一位都是一个u1类型的标志位,取值以下:
常量池中数据项类型 | 类型标志 | 类型描述 |
---|---|---|
CONSTANT_Utf8 | 1 | UTF-8编码的Unicode字符串 |
CONSTANT_Integer | 3 | int类型字面值 |
CONSTANT_Float | 4 | float类型字面值 |
CONSTANT_Long | 5 | long类型字面值 |
CONSTANT_Double | 6 | double类型字面值 |
CONSTANT_Class | 7 | 对一个类或接口的符号引用 |
CONSTANT_String | 8 | String类型字面值 |
CONSTANT_Fieldref | 9 | 对一个字段的符号引用 |
CONSTANT_Methodref | 10 | 对一个类中声明的方法的符号引用 |
CONSTANT_InterfaceMethodref | 11 | 对一个接口中声明的方法的符号引用 |
CONSTANT_NameAndType | 12 | 对一个字段或方法的部分符号引用 |
访问标志用来识别类或接口的访问信息,好比这个Class是类仍是接口,是public仍是private,是否被声明为final等。具体标志位及含义以下:
标志名称 | 标志值 | 含义 |
---|---|---|
ACC_PUBLIC | 0x00 01 | 是否为Public类型 |
ACC_FINAL | 0x00 10 | 是否被声明为final,只有类能够设置 |
ACC_SUPER | 0x00 20 | 是否容许使用invokespecial字节码指令的新语义. |
ACC_INTERFACE | 0x02 00 | 标志这是一个接口 |
ACC_ABSTRACT | 0x04 00 | 是否为abstract类型,对于接口或者抽象类来讲,次标志值为真,其余类型为假 |
ACC_SYNTHETIC | 0x10 00 | 标志这个类并不是由用户代码产生 |
ACC_ANNOTATION | 0x20 00 | 标志这是一个注解 |
ACC_ENUM | 0x40 00 | 标志这是一个枚举 |
咱们都知道,java中是单继承多实现的,除了Object类以外每一个类都有父类,所以它们是惟一的,而一个类能够实现多个接口,所以接口是不惟一的,用集合表示。类索引和父类索引都是用一个u2类型数据来表示的,而接口索引集合则是一组u2类型的数据表示。
类索引、父类索引和接口索引集合都按顺序排在访问标志的后面。类索引和父类索引u2类型的索引各指向一个类型为CONSTANT_Class_info的类描述符常量,用来描述具体的类。对于接口索引第一项u2则为接口索引计数器,用来记录实现了多少个接口,若是为0则后面再也不占用任何字节。
字段表用于声明类或接口中声明的变量。字段包括类变量和实例变量。在java中一个字段是如何描述的,举个的例子
public static final String num="13234";
能够看出来,首先是访问范围,是公有的仍是私有的,或者受保护的,这个信息决定了字段是否堆特定范围的类可见。
其次是一些关键字修饰的描述信息,是实例变量仍是类变量,是否可变,并发可见性,是否可被序列化等,这些关键字包括static、final 、volatile、transient等。
在后面即是字段的数据类型(基本数据类型、数组、对象)和名称。
上述的这些修饰符都是用布尔值来描述的,而数据类型和名称都是不肯定的,一般引用常量池的常量来描述。
字段表结构以下:
类型 | 名称 | 数量 |
---|---|---|
u2 | access_flags | 1 |
u2 | name_index | 1 |
u2 | descriptor_index | 1 |
u2 | attributes_count | 1 |
attribute_info | attributes | attributes_count |
字段修饰符在access_flag下,access_flag的内容以下:
标志名称 | 标志值 | 含义 |
---|---|---|
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_TRANSTENT | 0x0080 | 字段是否为transient |
ACC_SYNCHETIC | 0x1000 | 字段是否为由编译器自动产生 |
ACC_ENUM | 0x4000 | 字段是否为enum |
name_index表示的是字段的简单名称,像上面的简单名称就是“num”,descriptor_index 表示字段和方法的描述符。
描述符号的做用是用来描述字段的数据类型、方法的参数列表(数量、类型及顺序)和返回值。根据描述符的规则:基本数据类型以及表明无返回值的void类型都用一个大写的字符来表示,而对象类型则用字符L加对象的全限定名来描述,详情以下:
标志符 | 含义 |
---|---|
B | 基本数据类型byte |
C | 基本数据类型char |
D | 基本数据类型double |
F | 基本数据类型float |
I | 基本数据类型int |
J | 基本数据类型long |
S | 基本数据类型short |
Z | 基本数据类型boolean |
V | 基本数据类型void |
L | 对象类型 |
对于数组类型,每个维度用一个前置的“[”字符来描述,如定义个int[][]类型的二维数组,记录为:"[[I"。
用描述符来描述方法时,按照先参数列表后返回值的顺序描述。参数裂变按照参数顺序放在“()”内,如方法void login()描述符为“()V”,方法java.lang.String toString()的描述符为“()Ljava.lang.String”。
Class文件存储格式中对方法和字段的描述彻底一致,方法表的字段结构和字段表同样,包括访问标志、名称索引、描述符索引、属性表集合几项。这些数据的含义很是相似,在访问标志和属性表集合有所区别。
类型 | 名称 | 数量 |
---|---|---|
u2 | access_flags | 1 |
u2 | name_index | 1 |
u2 | descriptor_index | 1 |
u2 | attributes_count | 1 |
attribute_info | attributes | attributes_count |
voliate和transient不能修饰方法,因此访问标志没有了ACC_VOLATILE标志和ACC_TRANSIENT标志。一样,一些方法的关键字如:synchronized、native、strictfp、abstract等能够修饰方法,其标志位取值以下:
标志名称 | 标志值 | 含义 |
---|---|---|
ACC_PUBLIC | 0x00 01 | 方法是否为public |
ACC_PRIVATE | 0x00 02 | 方法是否为private |
ACC_PROTECTED | 0x00 04 | 方法是否为protected |
ACC_STATIC | 0x00 08 | 方法是否为static |
ACC_FINAL | 0x00 10 | 方法是否为final |
ACC_SYHCHRONRIZED | 0x00 20 | 方法是否为synchronized |
ACC_BRIDGE | 0x00 40 | 方法是不是有编译器产生的方法 |
ACC_VARARGS | 0x00 80 | 方法是否接受参数 |
ACC_NATIVE | 0x01 00 | 方法是否为native |
ACC_ABSTRACT | 0x04 00 | 方法是否为abstract |
ACC_STRICTFP | 0x08 00 | 方法是否为strictfp |
ACC_SYNTHETIC | 0x10 00 | 方法是不是有编译器自动产生的 |
方法的定义能够经过访问标志、名称和描述符索引来表述清除,那么方法中的代码又在哪里呢?咱们以前提到了属性表集合,方法里的java代码通过编译器编译成字节码指令后,存放在方法的属性表集合里一个名为“Code”的属性里。
咱们从上面不止一次的看到了属性表集合,不论是Class文件、字段表仍是方法表中,都有属性表集合(attribute_info)这一项,用于描述某些特定场景的专有信息。
与class文件其它数据项目严格要求顺序长度不一样,属性表集合限制相对比较宽松,不要求各个属性表具备严格顺序,只要不与已有属性名重复,任何人实现的编译器都可向属性表写入本身的属性,jvm运行时会自动忽略掉不认识的属性。
java7中定义的属性以下表:
属性名称 | 使用位置 | 含义 |
---|---|---|
Code | 方法表 | Java代码编译成的字节码指令 |
ConstantValue | 字段表 | final关键字定义的常量池 |
Deprecated | 类,方法,字段表 | 被声明为deprecated的方法和字段 |
Exceptions | 方法表 | 方法抛出的异常 |
EnclosingMethod | 类文件 | 仅当一个类为局部类或者匿名类是才能拥有这个属性,这个属性用于标识这个类所在的外围方法 |
InnerClass | 类文件 | 内部类列表 |
LineNumberTable | Code属性 | Java源码的行号与字节码指令的对应关系 |
LocalVariableTable | Code属性 | 方法的局部便狼描述 |
StackMapTable | Code属性 | JDK1.6中新增的属性,供新的类型检查检验器检查和处理目标方法的局部变量和操做数有所须要的类是否匹配 |
Signature | 类,方法表,字段表 | 用于支持泛型状况下的方法签名 |
SourceFile | 类文件 | 记录源文件名称 |
SourceDebugExtension | 类文件 | 用于存储额外的调试信息 |
Synthetic | 类,方法表,字段表 | 标志方法或字段为编译器自动生成的 |
LocalVariableTypeTable | 类 | 使用特征签名代替描述符,是为了引入泛型语法以后能描述泛型参数化类型而添加 |
RuntimeVisibleAnnotations | 类,方法表,字段表 | 为动态注解提供支持 |
RuntimeInvisibleAnnotations | 表,方法表,字段表 | 用于指明哪些注解是运行时不可见的 |
RuntimeVisibleParameterAnnotation | 方法表 | 做用与RuntimeVisibleAnnotations属性相似,只不过做用对象为方法 |
RuntimeInvisibleParameterAnnotation | 方法表 | 做用与RuntimeInvisibleAnnotations属性相似,做用对象哪一个为方法参数 |
AnnotationDefault | 方法表 | 用于记录注解类元素的默认值 |
BootstrapMethods | 类文件 | 用于保存invokeddynamic指令引用的引导方式限定符 |