Class类文件的结构

我们从头到尾将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^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  ........()
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_、lload、lload_、fload、fload_、dload、dload_、aload、aload_
    将一个数值从操作数栈存储到局部变量:istore、istore_、lstore、lstore_、fstore、fstore_、dstore、dstore_、astore、astore_
    将一个常量加载到操作数栈:bipush、sipush、ldc、ldc_w、ldc2_w、aconst_null、iconst_ml、iconst_、lconst、fconst_、dconst
    扩充局部变量表的访问索引的指令: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类文件的结构)