Class文件格式

2019-12-08  本文已影响0人  程序猿啊驼

 我们知道Java是一门跨平台的语言,我们编写的Java代码会被编译成中间class文件以让Java虚拟机解析运行。而Java虚拟机规范仅仅描述了抽象的Java虚拟机,在实现具体的Java虚拟机时,仅指出了设计规范。Java虚拟机的实现必须体现规范中的内容,但仅在确有必要时才应该受制于这些规范。对于完整内容,可以查看原文档,以JDK7为例,可查看https://docs.oracle.com/javase/specs/jvms/se7/html/,或者《深入理解Java虚拟机 JVM高级特性与最佳实践》一书。完整的规范主要包含以下内容:

 本文只是大概记录项目需要了解的基础概念,着重在介绍Class文件格式上,为该系列后续内容做铺垫。

 Class文件是一组以8字节为基础单位的二进制流,各个数据项目严格按照顺序紧凑排列在class文件中,中间没有任何分割符。每个 Class 文件都是由 8 字节为单位的字节流组成,所有的 16 位、32 位和 64 位长度的数据将被构造成 2 个、4 个和 8 个 8 字节单位来表示。

 每一个Class文件对应于一个如下所示的ClassFile结构体:

file

涉及到的内容包括:

 可用jdk自带的javap命令对class文件进行反编译,以查看内容,如下代码:

public class Ex {

    public void judgeAge(int age) {
        int step = 0;
        if (age > 18) {
            step++;
            System.out.println("a litter old");
        } else {
            System.out.println("a litter cute");
            step++;
        }
        System.out.println(step);
    }

    public static void main(String[] args) {
        Ex ex = new Ex();
        ex.judgeAge(16);
    }
}

执行 javap -verbose -p Ex.class的结果为

Classfile Ex.class
  Last modified 2019-11-29; size 788 bytes
  MD5 checksum 8b5d8ebf38c4441fe7150c10da31ce1b
  Compiled from "Ex.java"
public class Ex
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #10.#31        // java/lang/Object."<init>":()V
   #2 = Fieldref           #32.#33        // java/lang/System.out:Ljava/io/PrintStream;
   #3 = String             #34            // a litter old
   #4 = Methodref          #35.#36        // java/io/PrintStream.println:(Ljava/lang/String;)V
   #5 = String             #37            // a litter cute
   #6 = Methodref          #35.#38        // java/io/PrintStream.println:(I)V
   #7 = Class              #39            // Ex
   #8 = Methodref          #7.#31         // Ex."<init>":()V
   #9 = Methodref          #7.#40         // Ex.judgeAge:(I)V
  #10 = Class              #41            // java/lang/Object
  #11 = Utf8               <init>
  #12 = Utf8               ()V
  #13 = Utf8               Code
  #14 = Utf8               LineNumberTable
  #15 = Utf8               LocalVariableTable
  #16 = Utf8               this
  #17 = Utf8               LEx;
  #18 = Utf8               judgeAge
  #19 = Utf8               (I)V
  #20 = Utf8               age
  #21 = Utf8               I
  #22 = Utf8               step
  #23 = Utf8               StackMapTable
  #24 = Utf8               main
  #25 = Utf8               ([Ljava/lang/String;)V
  #26 = Utf8               args
  #27 = Utf8               [Ljava/lang/String;
  #28 = Utf8               ex
  #29 = Utf8               SourceFile
  #30 = Utf8               Ex.java
  #31 = NameAndType        #11:#12        // "<init>":()V
  #32 = Class              #42            // java/lang/System
  #33 = NameAndType        #43:#44        // out:Ljava/io/PrintStream;
  #34 = Utf8               a litter old
  #35 = Class              #45            // java/io/PrintStream
  #36 = NameAndType        #46:#47        // println:(Ljava/lang/String;)V
  #37 = Utf8               a litter cute
  #38 = NameAndType        #46:#19        // println:(I)V
  #39 = Utf8               Ex
  #40 = NameAndType        #18:#19        // judgeAge:(I)V
  #41 = Utf8               java/lang/Object
  #42 = Utf8               java/lang/System
  #43 = Utf8               out
  #44 = Utf8               Ljava/io/PrintStream;
  #45 = Utf8               java/io/PrintStream
  #46 = Utf8               println
  #47 = Utf8               (Ljava/lang/String;)V
{
  public Ex();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 1: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   LEx;

  public void judgeAge(int);
    descriptor: (I)V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=3, args_size=2
         0: iconst_0
         1: istore_2
         2: iload_1
         3: bipush        18
         5: if_icmple     22
         8: iinc          2, 1
        11: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
        14: ldc           #3                  // String a litter old
        16: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        19: goto          33
        22: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
        25: ldc           #5                  // String a litter cute
        27: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        30: iinc          2, 1
        33: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
        36: iload_2
        37: invokevirtual #6                  // Method java/io/PrintStream.println:(I)V
        40: return
      LineNumberTable:
        line 4: 0
        line 5: 2
        line 6: 8
        line 7: 11
        line 9: 22
        line 10: 30
        line 12: 33
        line 13: 40
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      41     0  this   LEx;
            0      41     1   age   I
            2      39     2  step   I
      StackMapTable: number_of_entries = 2
        frame_type = 252 /* append */
          offset_delta = 22
          locals = [ int ]
        frame_type = 10 /* same */

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=2, args_size=1
         0: new           #7                  // class Ex
         3: dup
         4: invokespecial #8                  // Method "<init>":()V
         7: astore_1
         8: aload_1
         9: bipush        16
        11: invokevirtual #9                  // Method judgeAge:(I)V
        14: return
      LineNumberTable:
        line 16: 0
        line 17: 8
        line 18: 14
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      15     0  args   [Ljava/lang/String;
            8       7     1    ex   LEx;
}
SourceFile: "Ex.java"

下面对后续需要接触到的几项内容做说明。

1. 数据项

 Class文件中有两种数据类型,分别是无符号数和表:

file

2. 访问和修饰符标识

file

 带有 ACC_SYNTHETIC 标志的部分,意味着它是由编译器自己产生的而不是由程序员编写的源代码生成的。有该标志的类、属性、方法等不会在源码中显示。

3. 类型描述符

file

 基本类型的描述是单个字符:

4. 方法描述符

 方法描述符是一个类型描述符列表,它用一个字符串描述一个方法的参数类型和返回类型。

 方法描述符以左括号开头,然后是每个形参的类型描述,然后是一个右括号,接下来是返回类型的类型描述符,如果该方法返回void,则是 V,表示方法描述中不包含方法的名字或参数名,可看如下例子:

file

5. 指令

 Java虚拟机的指令由一个字节长度的、代表着某种特定操作含义的操作码(Opcode)以及跟随其后的零至多个代表此操作所需参数的操作数(Operands)所构成。虚拟机中许多指令并不包含操作数,只有一个操作码。

 常见的指令如下:

 PS:笔者个人习惯使用Bytecode Outline进行反编译,这款插件输出的内容可读性会高点,上面的内容输出下:

// class version 52.0 (52)
// access flags 0x21
public class Ex {

  // compiled from: Ex.java

  // access flags 0x1
  public <init>()V
   L0
    LINENUMBER 1 L0
    ALOAD 0
    INVOKESPECIAL java/lang/Object.<init> ()V
    RETURN
   L1
    LOCALVARIABLE this LEx; L0 L1 0
    MAXSTACK = 1
    MAXLOCALS = 1

  // access flags 0x1
  public judgeAge(I)V
   L0
    LINENUMBER 4 L0
    ICONST_0
    ISTORE 2
   L1
    LINENUMBER 5 L1
    ILOAD 1
    BIPUSH 18
    IF_ICMPLE L2
   L3
    LINENUMBER 6 L3
    IINC 2 1
   L4
    LINENUMBER 7 L4
    GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
    LDC "a litter old"
    INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
    GOTO L5
   L2
    LINENUMBER 9 L2
   FRAME APPEND [I]
    GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
    LDC "a litter cute"
    INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
   L6
    LINENUMBER 10 L6
    IINC 2 1
   L5
    LINENUMBER 12 L5
   FRAME SAME
    GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
    ILOAD 2
    INVOKEVIRTUAL java/io/PrintStream.println (I)V
   L7
    LINENUMBER 13 L7
    RETURN
   L8
    LOCALVARIABLE this LEx; L0 L8 0
    LOCALVARIABLE age I L0 L8 1
    LOCALVARIABLE step I L1 L8 2
    MAXSTACK = 2
    MAXLOCALS = 3

  // access flags 0x9
  public static main([Ljava/lang/String;)V
   L0
    LINENUMBER 16 L0
    NEW Ex
    DUP
    INVOKESPECIAL Ex.<init> ()V
    ASTORE 1
   L1
    LINENUMBER 17 L1
    ALOAD 1
    BIPUSH 16
    INVOKEVIRTUAL Ex.judgeAge (I)V
   L2
    LINENUMBER 18 L2
    RETURN
   L3
    LOCALVARIABLE args [Ljava/lang/String; L0 L3 0
    LOCALVARIABLE ex LEx; L1 L3 1
    MAXSTACK = 2
    MAXLOCALS = 2
}

更多原创内容请搜索微信公众号:啊驼(doubaotaizi)

上一篇下一篇

猜你喜欢

热点阅读