美文网首页
Class类文件的结构

Class类文件的结构

作者: YoursBG | 来源:发表于2019-03-15 19:58 被阅读0次

    我们从头到尾将Class类文件的结构过一遍。
    注意:该结构没有任何分隔符,按照严格的顺序限定,由1到n,由n到m,都有其定义。

    • 魔术Magic
    类型 位置 数量 值(十六进制)
    u4 1-4字节 1 0xCAFEBABE

    据Java开发小组最初的关键成员Patrick Naughton所说“选择0xCAFEBABE是因为它象征着著名咖啡品牌Peet's Coffee中深受欢迎的Baristas咖啡”,这个魔术似乎预示着日后“Java”商标的出现。

    • 次版本号Minor Version
    类型 位置 数量 值(十六进制)
    u2 5-6字节 1 非固定值
    • 主版本号Major Version
    类型 位置 数量 值(十六进制)
    u2 7-8字节 1 非固定值

    每个JDK大版本发布主版本号向上加1,高版本的JDK能向下兼容以前版本的Class文件。虚拟机必须拒绝执行超过其版本号的Class文件。
    例如,JDK1.1支持45.0至45.65535的Class文件,JDK1.2也能支持45.0至46.65535的Class文件。

    为了方便理解,举个例子:
    创建一个Test.java文件

    class Test {
        public static void main(String[] args) {
            System.out.println("test");
        }
    }
    

    使用的javac版本号

    $ javac -version
    javac 1.8.0_161
    

    编译该文件,生成Test.class文件,vim打开Test.class文件

    $ javac Test.java
    $ vim -b Test.class
    
    ^@^F^@^O        ^@^P^@^Q^H^@^R
    ^@^S^@^T^G^@^U^G^@^V^A^@^F<init>^A^@^C()V^A^@^DCode^A^@^OLineNumberTable^A^@^Dmain^A^@^V([Ljava/lang/String;)V^A^@
    SourceFile^A^@  Test.java^L^@^G^@^H^G^@^W^L^@^X^@^Y^A^@^Dtest^G^@^Z^L^@^[^@^\^A^@^DTest^A^@^Pjava/lang/Object^A^@^Pjava/lang/System^A^@^Cout^A^@^ULjava/io/PrintStream;^A^@^Sjava/io/PrintStream^A^@^Gprintln^A^@^U(Ljava/lang/String;)V^@ ^@^E^@^F^@^@^@^@^@^B^@^@^@^G^@^H^@^A^@       ^@^@^@^]^@^A^@^A^@^@^@^E*·^@^A±^@^@^@^A^@
    ^@^@^@^F^@^A^@^@^@^A^@  ^@^K^@^L^@^A^@  ^@^@^@%^@^B^@^A^@^@^@   ²^@^B^R^C¶^@^D±^@^@^@^A^@
    ^@^@^@
    ^@^B^@^@^@^C^@^H^@^D^@^A^@^M^@^@^@^B^@^N
    

    命令模式下输入
    :%!xxd

    00000000: ca fe ba be 00 00 00 34 00 1d 0a 00 06 00 0f 09  .......4........
    00000010: 00 10 00 11 08 00 12 0a 00 13 00 14 07 00 15 07  ................
    00000020: 00 16 01 00 06 3c 69 6e 69 74 3e 01 00 03 28 29  .....<init>...()
    00000030: 56 01 00 04 43 6f 64 65 01 00 0f 4c 69 6e 65 4e  V...Code...LineN
    00000040: 75 6d 62 65 72 54 61 62 6c 65 01 00 04 6d 61 69  umberTable...mai
    00000050: 6e 01 00 16 28 5b 4c 6a 61 76 61 2f 6c 61 6e 67  n...([Ljava/lang
    00000060: 2f 53 74 72 69 6e 67 3b 29 56 01 00 0a 53 6f 75  /String;)V...Sou
    00000070: 72 63 65 46 69 6c 65 01 00 09 54 65 73 74 2e 6a  rceFile...Test.j
    00000080: 61 76 61 0c 00 07 00 08 07 00 17 0c 00 18 00 19  ava.............
    00000090: 01 00 04 74 65 73 74 07 00 1a 0c 00 1b 00 1c 01  ...test.........
    000000a0: 00 04 54 65 73 74 01 00 10 6a 61 76 61 2f 6c 61  ..Test...java/la
    000000b0: 6e 67 2f 4f 62 6a 65 63 74 01 00 10 6a 61 76 61  ng/Object...java
    000000c0: 2f 6c 61 6e 67 2f 53 79 73 74 65 6d 01 00 03 6f  /lang/System...o
    000000d0: 75 74 01 00 15 4c 6a 61 76 61 2f 69 6f 2f 50 72  ut...Ljava/io/Pr
    000000e0: 69 6e 74 53 74 72 65 61 6d 3b 01 00 13 6a 61 76  intStream;...jav
    000000f0: 61 2f 69 6f 2f 50 72 69 6e 74 53 74 72 65 61 6d  a/io/PrintStream
    00000100: 01 00 07 70 72 69 6e 74 6c 6e 01 00 15 28 4c 6a  ...println...(Lj
    00000110: 61 76 61 2f 6c 61 6e 67 2f 53 74 72 69 6e 67 3b  ava/lang/String;
    00000120: 29 56 00 20 00 05 00 06 00 00 00 00 00 02 00 00  )V. ............
    00000130: 00 07 00 08 00 01 00 09 00 00 00 1d 00 01 00 01  ................
    00000140: 00 00 00 05 2a b7 00 01 b1 00 00 00 01 00 0a 00  ....*...........
    00000150: 00 00 06 00 01 00 00 00 01 00 09 00 0b 00 0c 00  ................
    00000160: 01 00 09 00 00 00 25 00 02 00 01 00 00 00 09 b2  ......%.........
    00000170: 00 02 12 03 b6 00 04 b1 00 00 00 01 00 0a 00 00  ................
    00000180: 00 0a 00 02 00 00 00 03 00 08 00 04 00 01 00 0d  ................
    00000190: 00 00 00 02 00 0e 0a                             .......
    

    我们观察到,第一行的前8个字节是:ca fe ba be 00 00 00 34
    1-4字节是Magic,5-6字节是Minor Version,7-8字节是Major Version,主版本号0x0034的十进制是52,JDK1.8及以上支持52的Class文件。不同的JDK版本,编译生成的主次版本号不同(JDK版本对应支持Class版本可上网搜索)

    • 常量池

    9-10字节代表常量池容量计数值(constant_pool_count)
    我们看到,跟在ca fe ba be 00 00 00 34之后的是00 1d 0a 00 06 00 0f 09,0x001d的十进制是29,表示常量池中有28项常量,索引值1~28。注意:跟数组从0开始的习惯不同,0空出来的目的在于满足后面某些指向常量池的索引值的数据在特定情况下表示“不引用任何一个常量池项目”。

    类型 标识 描述
    Utf8 1 UTF-8编码字符串
    Integer 3 整型字面量
    Float 4 浮点型字面量
    Long 5 长整型字面量
    Double 6 双精度浮点型字面量
    Class 7 类或接口的符号引用
    String 8 字符串字面量
    Fieldref 9 字段的符号引用
    Methodref 10 类中方法的符合引用
    InterfaceMethodref 11 接口中方法的引用
    NameAndType 12 字段或方法部分符合引用
    MethodHandle 15 方法句柄
    MethodType 16 标识方法类型
    InvokeDynamic 18 动态方法调用

    • String
    type descriptor
    u1 tag
    u2 string_index

    • Methodref
    type descriptor
    u1 tag
    u2 class_index
    u2 name_and_type_index

    • 字段表集合

    字段表(field_info)包括的信息:
    字段的作用域修饰符:public、protected、private
    实例变量or类变量修饰符:static
    变量可变性修饰符:final
    并发可见性修饰符:volatile
    可否被序列化修饰符:transient
    字段数据类型:基本类型、对象、数组
    字段名称

    上面的信息中,修饰符都是boolean值,要么0,要么1。
    字段数据类型与字段名称就无法固定长度去表示。

    Java中,两个字段的数据类型、修饰符不管是否相同,都必须使用不一样的名称。但是对字节码没这个要求,两个字段的描述符不一致,字段重名就是合法的。

    • 方法表集合

    方法修饰符中,不包括volatile和transient。
    相对的,新增synchronized、native、strictfp和abstract

    • 属性表集合
    1. Code属性
    2. Exception属性
    3. LineNumberTable属性
    4. LocalVariableTable属性
    5. LocalVariableTypeTable属性(引入泛型时新增的属性)
    6. SourceFile属性
    7. ConstantValue属性
    8. InnerClasses属性
    9. Deprecated及Synthetic属性
    10. StackMapTable属性
    11. Signature属性
    12. BootstrapMethods属性
    • 字节码指令简介
    1. 字节码与数据类型
      在Java虚拟机的指令集中,大多数指令都包含其操作所对应的数据类型信息,例如iload指令用于从局部变量中加载int型的数据到操作数栈中。
      在指令集中,load有操作int的指令iload,但对byte、char、short和boolean,没有对应的指令,在处理它们时,会先转换成int类型,然后使用iload进行操作。
    2. 加载和存储指令
      加载和存储指令用于将数据在栈帧中的局部变量表和操作数栈之间来回传输。
      将一个局部变量加载到操作栈:iload、iload_<n>、lload、lload_<n>、fload、fload_<n>、dload、dload_<n>、aload、aload_<n>
      将一个数值从操作数栈存储到局部变量:istore、istore_<n>、lstore、lstore_<n>、fstore、fstore_<n>、dstore、dstore_<n>、astore、astore_<n>
      将一个常量加载到操作数栈:bipush、sipush、ldc、ldc_w、ldc2_w、aconst_null、iconst_ml、iconst_<i>、lconst<l>、fconst_<f>、dconst<d>
      扩充局部变量表的访问索引的指令:wide
    3. 运算指令
      加法指令:iadd、ladd、fadd、dadd
      减法指令:isub、lsub、fsub、dsub
      乘法指令:imul、lmul、fmul、dmul
      除法指令:idiv、ldiv、fdiv、ddiv
      求余指令:irem、lrem、frem、drem
      取反指令:ineg、lneg、fneg、dneg
      位移指令:ishl、ishr、iushr、lshl、lshr、lushr
      按位或指令:ior、lor
      按位与指令:iand、land
      按位异或指令:ixor、lxor
      局部变量自增指令:iinc
      比较指令:dcmpg、dcmpl、fcmpg、fcmpl、lcmp
    4. 类型转换指令
      宽化类型转换:
      int类型到long、float或者double类型
      long类型到float、double类型
      float类型到double类型
      窄化类型转换:
      i2b、i2c、i2s、l2i、f2i、f2l、d2i、d2l和d2f
    5. 对象创建于访问指令
      创建类实例指令:new
      创建数组指令:newarray、anewarray、multianewarray
      访问类字段指令:getfield、putfield、getstatic、putstatic
      把一个数组元素加载到操作数栈的指令:baload、caload、saload、iaload、laload、faload、daload、aaload
      将一个操作数栈的值存储到数组元素中的指令:bastore、castore、sastore、iastore、fastore、dastore、aastore
      取数组长度的指令:arraylength
      检查类实例类型的指令:instanceof、checkcast
    6. 操作数栈管理指令
      将操作数栈的栈顶一个或两个元素出栈:pop、pop2
      复制栈顶一个或两个数值并将复制值或双份的复制值重新压入栈顶:dup、dup2、dup_x1、dup2_x1、dup_x2、dup2_x2
      将栈最顶端的两个数值交换:swap
    7. 控制转移指令
      条件分支:ifeq、iflt、ifle、ifne、ifgt、ifge、ifnull、ifnonnull、if_icmpeq、if_icmpne、if_icmplt、if_icmpgt、if_icmple、if_icmpge、if_acmpeq和if_acmpne
      复合条件分支:tableswitch、lookupswitch
      无条件分支:goto、goto_w、jsr、jsr_w、ret
    8. 方法调用和返回指令
      invokevirtual指令用于调用对象的实例方法
      invokeinterface指令用于调用接口方法
      invokespecial指令用于调用一些需要特殊处理的实例方法
      invokestatic指令用于调用类方法
      invokedynamic指令用于在运动时动态解析出调用点限定符所引用的方法
      注:前四条调用指令的分派逻辑都固化在Java虚拟机内部,而invokedynamic指令的分派逻辑是由用户所设定的引导方法决定的。
    9. 异常处理指令
      显式抛出异常指令athrow
    10. 同步指令
      Java虚拟机支持方法级同步和方法内部一段指令序列的同步,都是通过管程(Monitor)来支持的。
      指令:monitorenter、monitorexit

    相关文章

      网友评论

          本文标题:Class类文件的结构

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