美文网首页
Class文件

Class文件

作者: 趁现在赶快回家 | 来源:发表于2017-05-12 21:52 被阅读0次

    Java的跨平台特性建立在Java虚拟机之上。

    • Java虚拟机在不同平台上有不同的版本,但是他们都能执行同一class文件。
    • 任何编程语言的源代码,只要能编译成class文件,都能运行在Java虚拟机上面。例如Groovy语言。

    1. Class文件结构

    Class文件在Java虚拟机规范中的定义如下:

    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 Class文件总体结构<br>摘自《实战Java虚拟机:JVM故障诊断与性能优化》 ——葛一鸣

    下面来逐一说明Class文件中的每一种结构。

    1.1 常量池

    常量池中有很多种常量,有一种可以通用来描述它们的结构:

    cp_info {
        u1 tag;
        u1 info[];
    }
    

    每一种常量都以一个描述当前常量类型的u1(8字节无符号)整数tag开始,后面接上根据不同常量类型变化的2个或多个字节组成的,用来描述该常量携带的具体信息的东东。比如CONSTANT_Class_info常量

    CONSTANT_Class_info {
        u1 tag;
        u2 name_index;
    }
    

    它的info[]就是u2(2字节无符号整数)表示的name_index。而CONSTANT_Integer_info的info[]则是u4(4字节无符号整数)表示的整数具体值:

    CONSTANT_Integer_info {
        u1 tag;
        u4 bytes;
    }
    

    截止到Java7,常量池包含了14种常量,他们各自的tag值如下表:

    <h6 align = "center">表1 常量类型及其tag值</h6>

    Constant Type Value
    CONSTANT_Class 7
    CONSTANT_Fieldref 9
    CONSTANT_Methodref 10
    CONSTANT_InterfaceMethodref 11
    CONSTANT_String 8
    CONSTANT_Integer 3
    CONSTANT_Float 4
    CONSTANT_Long 5
    CONSTANT_Double 6
    CONSTANT_NameAndType 12
    CONSTANT_Utf8 1
    CONSTANT_MethodHandle 15
    CONSTANT_MethodType 16
    CONSTANT_InvokeDynamic 18

    总的来说,常量池中的常量包括字面量和符号引用两类

    • 字面量。底层的数据类型,包括数字常量CONSTANT_Integer、CONSTANT_Float、CONSTANT_Long以及CONSTANT_Double和字符串常量CONSTANT_Utf8。它们的值就存储在各自的info[]之中。
    • 符号引用。包括类和接口名、字段和方法信息等。他们都通过索引直接或间接地指向CONSTANT_Utf8常量。

    1.1.1 CONSTANT_Class

    The CONSTANT_Class_info structure is used to represent a class or an interface:

    CONSTANT_Class_info {
        u1 tag;
        u2 name_index;
    }
    

    CONSTANT_Class类型的常量,通过name_index指向常量池中的一个CONSTANT_Utf8常量,用来表示自己所代表的类或接口名。

    由于数组同样是对象,CONSTANT_Class表示数组的方式与方法与域的描述符中数组表示方式一样,如int[][]表示成[[I,Thread[]表示成[Ljava/lang/Thread。

    1.1.2 CONSTANT_NameAndType

    The CONSTANT_NameAndType_info structure is used to represent a field or method, without indicating which class or interface type it belongs to:

    CONSTANT_NameAndType_info {
        u1 tag;
        u2 name_index;
        u2 descriptor_index;
    }
    

    1.1.3 CONSTANT_Fieldref, CONSTANT_Methodref, and CONSTANT_InterfaceMethodref

    分别表示Fields, methods, and interface methods引用,结构相似:

    CONSTANT_Fieldref_info {
        u1 tag;
        u2 class_index;
        u2 name_and_type_index;
    }
    
    CONSTANT_Methodref_info {
        u1 tag;
        u2 class_index;
        u2 name_and_type_index;
    }
    
    CONSTANT_InterfaceMethodref_info {
        u1 tag;
        u2 class_index;
        u2 name_and_type_index;
    }
    

    由于CONSTANT_Class和CONSTANT_NameAndType可以完全确定一个字段或者方法,所以Fields, methods, and interface methods引用都含有:

    • class_index。指向CONSTANT_Class_info常量,代表该field或method所在的类,或者interface method所在的接口。
    • name_and_type_index。指向CONSTANT_NameAndType常量。

    所以,CONSTANT_Fieldref, CONSTANT_Methodref, and CONSTANT_InterfaceMethodref与CONSTANT_Class和CONSTANT_NameAndType以及CONSTANT_Utf8的引用关系大致如下:

    图2 常量引用关系<br>摘自 《自己动手写Java虚拟机》——张秀宏

    常量池疑问

    1. 不太明白的是,为什么有了CONSTANT_Utf8还要CONSTANT_String,其实CONSTANT_String也是指向CONSTANT_Utf8的啊?
    2. CONSTANT_MethodHandle、CONSTANT_MethodType、CONSTANT_InvokeDynamic是Java7新加入的,它们用来干啥?我还没学到这儿来……

    1.2 字段与方法

    如图1所示,类的字段与方法具有相似的结构。以字段为例,字段包括字段数目以及字段具体信息数组:

    u2             fields_count;
    field_info     fields[fields_count];
    

    如图1中看到的那样,字段具体信息的具体结构:

    field_info {
        u2             access_flags;
        u2             name_index;
        u2             descriptor_index;
        u2             attributes_count;
        attribute_info attributes[attributes_count];
    }
    
    • access_flags,字段访问标志
    • name_index,字段名称,再次无情指向常量池中的CONSTANT_Utf8。
    • descriptor_index,字段类型,同样指向常量池中的CONSTANT_Utf8。
    • attributes_count,属性数组的长度。一个字段可能拥有一些属性,用于存储额外信息,如初始化值、注释信息等。
    • attributes[attributes_count],属性数组。

    字段疑问

    1. fields与常量池中的CONSTANT_Fieldref的关系?
    2. 看到field的属性中也许带有初始化值,突然想到《Java编程思想》中的一道题,定义2个String类型的字段,问在定义的时候初始化与在构造函数中初始化有什么不同?
      我当时编写了这样的程序:
    public class Task2 {
            public String s1="has initialized";
            public String s2;
            public Task2(){
                s2="initialized in constructor";
            }
        }
    

    编译之后,通过javap -c Task2 查看:

          Compiled from "Task2.java"
          public class peris.sky.learn.Task2 {
          public java.lang.String s1;
    
          public java.lang.String s2;
    
           public peris.sky.learn.Task2();
              Code:
                 0: aload_0
                 1: invokespecial #1                  // Method java/lang/Object."<init>":()V
                 4: aload_0
                 5: ldc           #2                  // String has initialized
                 7: putfield      #3                  // Field s1:Ljava/lang/String;
                10: aload_0
                11: ldc           #4                  // String initialized in constructor
                13: putfield      #5                  // Field s2:Ljava/lang/String;
                16: return
          }
    

    发现,s1与s2的初始化其实都是在构造函数中完成的,只是先后顺序不一样。
    学了class文件的字段之后,利用classpy查看class文件,发现s1与s2的属性表中,并没有初始化值,于是仔细阅读java虚拟机规范,发现这个初始化值只有常量(static and final)类型的field的属性表中才有。

    1.3 属性

    属性出现在class文件中的类属性表、字段属性表、方法属性表以及方法中的Code属性的属性表四种地方。属性与常量池中的常量类似,有多种类型,但都有相似的结构:

    attribute_info {
        u2 attribute_name_index;
        u4 attribute_length;
        u1 info[attribute_length];
    }
    

    其中:

    • attribute_name_index,指向常量池中的CONSTANT_Utf8_info常量,表示该属性的类型(如表2所示)。
    • attribute_length,指出下一项info[]的长度。
    • info[attribute_length], 属性的具体信息,这个是跟随属性类型的不同而变化的。

    <h6 align = "center">表2 属性类型、其可能出现的位置以及含义</h6>

    AttributeType classfile field_info method_info Code 含义
    ConstantValue y 常量字段的值
    Code y 方法执行的字节码
    StackMapTable y 类型检查的时候用
    Exceptions y 方法可能抛出的一样信息
    InnerClasses y 内部类
    EnclosingMethod y
    Synthetic y y y 源文件中不存在的类成员
    Signature y y y
    SourceFile y 源文件名
    SourceDebugExtension y
    LineNumberTable y 方法的行号
    LocalVariableTable y 方法的局部变量信息
    LocalVariableTypeTable y
    Deprecated y y y 指出不建议使用的东东
    RuntimeVisibleAnnotations y y y
    RuntimeInvisibleAnnotations y y y
    RuntimeVisibleParameterAnnotations y
    RuntimeInvisibleParameterAnnotations y
    AnnotationDefault y
    BootstrapMethods y

    1.3.1 Code属性

    Code只存在于method_info之中,Code属性中存放字节码等方法相关信息,正如前面所说的那样,Code属性中还可以有其它属性,所以比较麻烦。它的结构如下:

    Code_attribute {
        u2 attribute_name_index;
        u4 attribute_length;
        u2 max_stack;
        u2 max_locals;
        u4 code_length;
        u1 code[code_length];
        u2 exception_table_length;
        {   u2 start_pc;
            u2 end_pc;
            u2 handler_pc;
            u2 catch_type;
        } exception_table[exception_table_length];
        u2 attributes_count;
        attribute_info attributes[attributes_count];
    }
    

    可以看出,Code的info[]内容非常丰富:

    • max_stack,方法执行过程中,操作数栈不停地变化,在整个执行过程中存在着一个最大深度,这是在编译过程中决定的。
    • max_locals,局部变量表的最大size。相比于操作数栈,局部变量表像一个数组。
    • code[code_length],这个就是方法最重要的字节码咯,jvm把它翻译成一个又一个的jvm指令,然后就可以执行方法了。
    • exception_table,异常表,表中每一项是由start_pc、end_pc、handler_pc以及catch_type组成的结构体那样的东东。从方法字节码的start_pc偏移量开始到end_pc偏移量为止的这段代码中,如果遇到catch_type所指向的异常,那么代码就跳转到handler_pc的位置执行,说白了,就是写代码的try-catch结构。
    • attributes[attributes_count],Code属性的属性。从表2中可以看出Code属性可以拥有包括StackMapTable、LineNumberTable、LocalVariableTable以及LocalVariableTypeTable在内的4中属性。其中LineNumberTableLocalVariableTable以及LocalVariableTypeTable三个与类的SourceFile属性一起是调试时使用的。比如,LineNumberTable用来指定字节码与源文件中的行号的对应关系。 StackMapTable是用来class文件的类型检验。这4个属性都不是运行时必需的。

    相关文章

      网友评论

          本文标题:Class文件

          本文链接:https://www.haomeiwen.com/subject/jfygtxtx.html