深入理解JVM系列(一)类文件格式

本文介绍Java虚拟机的类文件格式。每个类文件都包含一个类或接口的定义。虽然类或接口不需要有字面上包含在文件中的外部表示(例如,因为类是由类装入器生成的),但我们口头上将类或接口的任何有效表示称为类文件格式。

java文件从编码到执行

image.png

1、首先我们有个x.java文件,通过javac命令编译成x.class文件
2、执行java命令将x.class由ClassLoader加载到内存里
3、通常我们在写代码的时候用到的java类库也会被加载到内存中
4、调用字节码解释器或JIT即时编译器进行解释或编译
5、编译完之后由执行引擎通过os的硬件执行

通常java都是使用解释和编译混合模式,非常常用的代码就会由JIT(即时编译器)编译,下次再执行的时候就不要解释器再一句一句的解释执行,效率会高很多,执行引擎可以直接交给操作系统调用,但是不是所有的代码都由JIT编译,这样就不能跨平台了,所以java使用混合模式。


image.png

JVM和语言无关

JVM和java并不是强绑定的,JVM只跟class格式的文件有关系,只要可以编译出符合class规范的文件,都可以在JVM上运行,所以JVM是定义了一种规范。目前可以运行在JVM上的语言有很多,比如Scala、groovy等等。


image.png

类文件结构

类文件由单一的类文件结构组成:

ClassFile {
    u4             magic; 
    u2             minor_version;
    u2             major_version;
    u2             constant_pool_count;
    cp_info        constant_pool[constant_pool_count-1];
    u2             access_flags;
    u2             this_class;
    u2             super_class;
    u2             interfaces_count;
    u2             interfaces[interfaces_count];
    u2             fields_count;
    field_info     fields[fields_count];
    u2             methods_count;
    method_info    methods[methods_count];
    u2             attributes_count;
    attribute_info attributes[attributes_count];
}

类文件结构中的项如下:

  1. magic(魔数)
    每个Java class 文件的前4个字节被称为他的魔数(magic number):0x CAFEBABE 。魔数的作用在于,可以轻松的分辨出java class文件和非java class 文件。如果一个文件不是以0xCAFEBABE 开头的,那他肯定不是java class文件。

  2. minor_version 和 major_version
    minor_version和major_version项的值是这个类文件的主版本号和次版本号。主版本号和次版本号一起决定类文件格式的版本。如果一个类文件的主版本号为M,次版本号为M,则我们将其类文件格式的版本表示为M. M。因此,类文件格式的版本可以按字典顺序排列,例如,1.5 < 2.0 < 2.1。比如java8就是52.0其中major_version就是52,minor_version就是0。

  3. constant_pool.count 和 constant_pool
    class 文件中,魔数和版本号后面的是常量池。constant_pool是一个结构表,它代表了各种字符串常量,类和接口名,字段名,以及在类文件结构及其子结构中引用的其他常量。每个constant_pool表项的格式都由它的第一个“tag”字节表示。constant_pool表的索引从1到constant_pool_count - 1。 常量池中的类型如下:

    入口类型 标志值 描述
    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 对一个字段或方法的部分符号引用
    CONSTANT_MethodHandle 15 方法句柄
    CONSTANT_MethodType 16 方法类型
    CONSTANT_InvokeDynamic 18 动态调用
  4. access_flags
    紧接常量池后面的两个字节称为 access_flags ,access_flags项的值是一个标记的掩码,用于表示该类或接口的访问权限和属性。下表规定了每个标志设置后的解释。

    标志名 设置后的含义 设置名
    ACC_PUBLIC 0x0001 public 类型 类和接口
    ACC_FINAL 0x0010 类为final类型 只有类
    ACC_SUPER 0x0020 使用新型的invokespecial语义 类和接口
    ACC_INTERFACE 0x0200 接口类型,不是类 类型 所有的接口,没有类
    ACC_ABSTRACT 0x0400 abstract类型 所有的接口,部分类
  5. this_class
    接下来的两个字节为this_class项,它是一个队常量池的索引。this_class项的值必须是constant_pool表的有效索引。索引处的constant_pool条目必须是CONSTANT_Class_info结构,它表示这个类文件定义的类或接口。

  6. super_class
    在class文件中,紧接在this_class 之后的是 super_class 项,它是一个两个字节的常量池索引。对于一个类,super_class项的值必须为零或者必须是constant_pool表的有效索引。
    如果super_class项的值为非零,则该索引处的constant_pool项必须是CONSTANT_Class_info结构,它表示这个类文件定义的类的直接超类。无论是直接超类还是它的任何超类都不能在它的类文件结构的access_flags项中设置ACC_FINAL标志。
    如果super_class项的值为0,那么这个类文件必须表示class对象,这是唯一没有直接超类的类或接口。
    对于接口,super_class项的值必须始终是constant_pool表的有效索引。索引处的constant_pool条目必须是表示类对象的CONSTANT_Class_info结构。

  7. interfaces_count 和 intefaces
    紧接着 super_class 的是 interfaces_count。此项的含义为:在文件中该类直接实现或者由接口所扩展的父接口的数量。在这个计数的后面,是名为 interfaces 的数组,它包含了对每个由该类或者接口直接实现的父接口的常量池索引。

  8. fields_count 和 fields
    在class文件中,紧接在 interfaces 后面的是对在该类或者接口中所声明的字段的描述。首先是名为 fields_count 的计数,它是类变量和实例变量的字段的数量总和。在这个计数后面的是不同长度的 field_info 表的序列。只有在文件中由类或者接口声明了的字段才能在 fields 列表中列出。在 fields 列表中,不列出从超类或者父接口继承而来的字段。另一方面,fields 列表可能会包含在对应的java源文件中没有叙述的字段,这是因为java编译器可能会在编译时向类或者接口添加字段,添加进去的字段使用 Synthetic 属性标识。fields_descriptor 标识对应如所示:

    基本类型 含义
    B byte
    C char
    D double
    F float
    I int
    J long
    S short
    Z boolean
    V void
    L Object
    [ array(例如:[B ,表示 byte[] [Ljava/lang/String 表示 string[] ,多维数组 [[C [[[Ljava/lang/String)
  9. methods_count 和 methods
    在class文件中紧接着fields 后面的是对该类或者接口中所声明的方法的描述。首先是名为 methods_count 的计数,它是一个双字节长度的对于该类或者接口中声明的所有方法的总计数。这个总计数只包含在该类或者接口中显式定义的方法(从超类或者父接口中集成来的方法不被计入)。
    方法表中的每个值都必须是method_info结构,该结构给出了这个类或接口中方法的完整描述。如果在method_info结构的access_flags项中都没有设置ACC_NATIVE和ACC_ABSTRACT标志,那么还会提供实现该方法的Java虚拟机指令。

method_info结构表示由这个类或接口类型声明的所有方法,包括实例方法、类方法、实例初始化方法和任何类或接口初始化方法。方法表不包括表示从超类或超接口继承的方法的项。
如下所示:


image.png

图中字节码部分其实就是java 的汇编语言,在 jvms 8 的第六、七章有说明,jvms 的下载地址为 https://docs.oracle.com/javase/specs/index.html,值得注意的是一定要注意版本。

  1. attributes_count 和 attributes
    class 文件中最后的部分是属性(attribute),他给出了在改文件中类或者接口所定义的属性的基本信息。属性部分由 attributes_count 开始,attributes_count 是指出现在后续 attributes 列表中的 attributes_info 表的数量总和。属性表的每个值都必须是一个attribute_info结构,每个 attributes_info 的第一项是指向常量池中 CONSTANT_Utf8_info表的索引,该表给出了属性的名称。

你可能感兴趣的:(深入理解JVM系列(一)类文件格式)