JVM第一课:如何阅读Class文件

2024-05-21 16:58:11 浏览数 (1)

本文以 JDK8为例,讲解 Class 文件结构。

前言

Oracle 有两个产品实现了 Java SE (Java Platform Standard Edition) 8,分别为Java SE Development Kit (JDK) 8和 Java SE Runtime Environment (JRE) 8。

JDK 8是 JRE 8的超集,包含 JRE 8中的所有内容,以及开发 applet 和应用程序所需的编译器和调试器等工具。

而 JRE 8提供库、Java 虚拟机(JVM)和其他组件来运行用 Java 编程语言编写的 applet 和应用程序。请注意,JRE 包括 Java SE 规范不需要的组件,包括标准组件和非标准组件。

简而言之,JDK 适用于开发,JRE 仅仅适用于 applet 和应用程序的部署运行,及在服务器环境下可以只使用 JRE。

class 文件的产生

*.class 文件是 *.java 文件编译后的形成的中间文件。下面使用一个小例子来看一下 class 文件的结构。

例:我们有以下的源代码文件—— Main.java

代码语言:javascript复制
package com.ber;  
  
public class Main {  
    public static void main(String[] args) {  
        Integer i1 = 10;  
        Integer i2 = 10;  
        System.out.println(i1 == i2);  
  
        Integer i3 = 128;  
        Integer i4 = 128;  
        System.out.println(i3 == i4);  
  
        Boolean b1 = true;  
        Boolean b2 = true;  
        System.out.println(b1 == b2);  
  
        Double d1=1.00;  
        Double d2=1.00;  
        System.out.println(d1 == d2);  
    }  
}

编译 javac Main.java ---> Main.class

补充点编译细节:Main.java -> 词法分析器 -> tokens 流 -> 语法分析器 -> 语法树/抽象语法树 -> 语义分析器 -> 注解抽象语法树 -> 字节码生成器 -> Main.class 文件。这里就不展开细说啦。

经过编译后,我们得到了 Main.class 文件,如果要打开这个文件我们会看到一串一串字符乱码,这些字符是无法直接取阅读的。通过反编译后,就可以得到类似于源码的代码,如下。

代码语言:javascript复制
public class com.ber.Main
  minor version: 0
  major version: 52
  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
  this_class: #7                          // com/ber/Main
  super_class: #8                         // java/lang/Object
  interfaces: 0, fields: 0, methods: 2, attributes: 1
Constant pool:
   #1 = Methodref          #8.#39         // java/lang/Object."<init>":()V
   #2 = Methodref          #33.#40        // java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
   #3 = Fieldref           #41.#42        // java/lang/System.out:Ljava/io/PrintStream;
   #4 = Methodref          #34.#43        // java/io/PrintStream.println:(Z)V
   #5 = Methodref          #35.#44        // java/lang/Boolean.valueOf:(Z)Ljava/lang/Boolean;
   #6 = Methodref          #36.#45        // java/lang/Double.valueOf:(D)Ljava/lang/Double;
   #7 = Class              #46            // com/ber/Main
   #8 = Class              #47            // java/lang/Object
   #9 = Utf8               <init>
  #10 = Utf8               ()V
  #11 = Utf8               Code
  #12 = Utf8               LineNumberTable
  #13 = Utf8               LocalVariableTable
  #14 = Utf8               this
  #15 = Utf8               Lcom/ber/Main;
  #16 = Utf8               main
  #17 = Utf8               ([Ljava/lang/String;)V
  #18 = Utf8               args
  #19 = Utf8               [Ljava/lang/String;
  #20 = Utf8               i1
  #21 = Utf8               Ljava/lang/Integer;
  #22 = Utf8               i2
  #23 = Utf8               i3
  #24 = Utf8               i4
  #25 = Utf8               b1
  #26 = Utf8               Ljava/lang/Boolean;
  #27 = Utf8               b2
  #28 = Utf8               d1
  #29 = Utf8               Ljava/lang/Double;
  #30 = Utf8               d2
  #31 = Utf8               StackMapTable
  #32 = Class              #19            // "[Ljava/lang/String;"
  #33 = Class              #48            // java/lang/Integer
  #34 = Class              #49            // java/io/PrintStream
  #35 = Class              #50            // java/lang/Boolean
  #36 = Class              #51            // java/lang/Double
  #37 = Utf8               SourceFile
  #38 = Utf8               Main.java
  #39 = NameAndType        #9:#10         // "<init>":()V
  #40 = NameAndType        #52:#53        // valueOf:(I)Ljava/lang/Integer;
  #41 = Class              #54            // java/lang/System
  #42 = NameAndType        #55:#56        // out:Ljava/io/PrintStream;
  #43 = NameAndType        #57:#58        // println:(Z)V
  #44 = NameAndType        #52:#59        // valueOf:(Z)Ljava/lang/Boolean;
  #45 = NameAndType        #52:#60        // valueOf:(D)Ljava/lang/Double;
  #46 = Utf8               com/ber/Main
  #47 = Utf8               java/lang/Object
  #48 = Utf8               java/lang/Integer
  #49 = Utf8               java/io/PrintStream
  #50 = Utf8               java/lang/Boolean
  #51 = Utf8               java/lang/Double
  #52 = Utf8               valueOf
  #53 = Utf8               (I)Ljava/lang/Integer;
  #54 = Utf8               java/lang/System
  #55 = Utf8               out
  #56 = Utf8               Ljava/io/PrintStream;
  #57 = Utf8               println
  #58 = Utf8               (Z)V
  #59 = Utf8               (Z)Ljava/lang/Boolean;
  #60 = Utf8               (D)Ljava/lang/Double;
{
  public com.ber.Main();
    descriptor: ()V
    flags: (0x0001) 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 3: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lcom/ber/Main;

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
    Code:
      stack=3, locals=9, args_size=1
         0: bipush        10
         2: invokestatic  #2                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
         5: astore_1
         6: bipush        10
         8: invokestatic  #2                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
        11: astore_2
        12: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
        15: aload_1
        16: aload_2
        17: if_acmpne     24
        20: iconst_1
        21: goto          25
        24: iconst_0
        25: invokevirtual #4                  // Method java/io/PrintStream.println:(Z)V
        28: sipush        128
        31: invokestatic  #2                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
        34: astore_3
        35: sipush        128
        38: invokestatic  #2                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
        41: astore        4
        43: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
        46: aload_3
        47: aload         4
        49: if_acmpne     56
        52: iconst_1
        53: goto          57
        56: iconst_0
        57: invokevirtual #4                  // Method java/io/PrintStream.println:(Z)V
        60: iconst_1
        61: invokestatic  #5                  // Method java/lang/Boolean.valueOf:(Z)Ljava/lang/Boolean;
        64: astore        5
        66: iconst_1
        67: invokestatic  #5                  // Method java/lang/Boolean.valueOf:(Z)Ljava/lang/Boolean;
        70: astore        6
        72: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
        75: aload         5
        77: aload         6
        79: if_acmpne     86
        82: iconst_1
        83: goto          87
        86: iconst_0
        87: invokevirtual #4                  // Method java/io/PrintStream.println:(Z)V
        90: dconst_1
        91: invokestatic  #6                  // Method java/lang/Double.valueOf:(D)Ljava/lang/Double;
        94: astore        7
        96: dconst_1
        97: invokestatic  #6                  // Method java/lang/Double.valueOf:(D)Ljava/lang/Double;
       100: astore        8
       102: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
       105: aload         7
       107: aload         8
       109: if_acmpne     116
       112: iconst_1
       113: goto          117
       116: iconst_0
       117: invokevirtual #4                  // Method java/io/PrintStream.println:(Z)V
       120: return
      LineNumberTable:
        line 5: 0
        line 6: 6
        line 7: 12
        line 9: 28
        line 10: 35
        line 11: 43
        line 13: 60
        line 14: 66
        line 15: 72
        line 17: 90
        line 18: 96
        line 19: 102
        line 20: 120
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0     121     0  args   [Ljava/lang/String;
            6     115     1    i1   Ljava/lang/Integer;
           12     109     2    i2   Ljava/lang/Integer;
           35      86     3    i3   Ljava/lang/Integer;
           43      78     4    i4   Ljava/lang/Integer;
           66      55     5    b1   Ljava/lang/Boolean;
           72      49     6    b2   Ljava/lang/Boolean;
           96      25     7    d1   Ljava/lang/Double;
          102      19     8    d2   Ljava/lang/Double;
      StackMapTable: number_of_entries = 8
        frame_type = 255 /* full_frame */
          offset_delta = 24
          locals = [ class "[Ljava/lang/String;", class java/lang/Integer, class java/lang/Integer ]
          stack = [ class java/io/PrintStream ]
        frame_type = 255 /* full_frame */
          offset_delta = 0
          locals = [ class "[Ljava/lang/String;", class java/lang/Integer, class java/lang/Integer ]
          stack = [ class java/io/PrintStream, int ]
        frame_type = 255 /* full_frame */
          offset_delta = 30
          locals = [ class "[Ljava/lang/String;", class java/lang/Integer, class java/lang/Integer, class java/lang/Integer, class java/lang/Integer ]
          stack = [ class java/io/PrintStream ]
        frame_type = 255 /* full_frame */
          offset_delta = 0
          locals = [ class "[Ljava/lang/String;", class java/lang/Integer, class java/lang/Integer, class java/lang/Integer, class java/lang/Integer ]
          stack = [ class java/io/PrintStream, int ]
        frame_type = 255 /* full_frame */
          offset_delta = 28
          locals = [ class "[Ljava/lang/String;", class java/lang/Integer, class java/lang/Integer, class java/lang/Integer, class java/lang/Integer, class java/lang/Boolean, class java/lang/Boolean ]
          stack = [ class java/io/PrintStream ]
        frame_type = 255 /* full_frame */
          offset_delta = 0
          locals = [ class "[Ljava/lang/String;", class java/lang/Integer, class java/lang/Integer, class java/lang/Integer, class java/lang/Integer, class java/lang/Boolean, class java/lang/Boolean ]
          stack = [ class java/io/PrintStream, int ]
        frame_type = 255 /* full_frame */
          offset_delta = 28
          locals = [ class "[Ljava/lang/String;", class java/lang/Integer, class java/lang/Integer, class java/lang/Integer, class java/lang/Integer, class java/lang/Boolean, class java/lang/Boolean, class java/lang/Double, class java/lang/Double ]
          stack = [ class java/io/PrintStream ]
        frame_type = 255 /* full_frame */
          offset_delta = 0
          locals = [ class "[Ljava/lang/String;", class java/lang/Integer, class java/lang/Integer, class java/lang/Integer, class java/lang/Integer, class java/lang/Boolean, class java/lang/Boolean, class java/lang/Double, class java/lang/Double ]
          stack = [ class java/io/PrintStream, int ]
}
SourceFile: "Main.java"

补充:反编译工具

  1. javap
  2. IDE 工具:如 Idea 工具栏中 View---'show bytecode'。
  3. Idea 插件—— jclasslib Bytecode Viewer,最直观展示信息。

javap 是 Java 开发工具包(JDK)提供的一个命令行工具,用于反编译 Java 字节码。javap 可以将 Java 类文件解析为易于阅读的文本形式,展示其中的信息以及反编译出类的结构、方法、字段、常量池等信息。通过阅读和分析这些信息,开发人员可以更好地理解 Java 类的内部实现,并进行性能调优、代码审查等操作。

我们可以使用 jclasslib Bytecode Viewer 插件,直观的阅读 class 文件结构,包括 class 文件格式的主次版本号、常量池等信息。

class 文件结构介绍

每个 class 文件包含一个类或接口的定义。

类文件由字节流组成,既我们所看到的一串一串字符。所有 16 位、32 位和 64 位的变量也都是通过读入 2 个、4 个和 8 个连续的字节构建的。并且多字节数据项总是按大端模式存储,即高字节位在前。

如下是 oracle 官方给的 class 文件结构

代码语言:javascript复制
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];
}

案例中的数据类型表示为 u1u2u4 类型分别代表一个、两个或四个字节的无符号量,这些类型可通过 java. Io. DataInput 接口的 readUnsignedByte、readUnsignedShort 和 readInt 等方法读取。

下面介绍下 class 文件结构的具体含义。

类型

名称

数量

说明

u4

magic

1

魔数:确定一个文件是否是 Class 文件。值是固定的为 0xCAFEBABE

u2

minor_version

1

Class 文件的次版本号,与主版本号共同决定 class 文件格式的版本。如果一个类文件的主要版本号为 M,次要版本号为 m,我们就用 M.m 表示其类文件格式的版本。

u2

major_version

1

Class 文件的主版本号:一个 JVM 实例只能支持特定范围内版本号的 Class 文件(可以向下兼容)。

u2

constant_pool_count

1

常量表数量,constant_pool_count 项的值等于 constant_pool 表中的条目数加1。如果一个常量大于0且小于 constant_pool_count 则被认为是有效的。

cp_info

constant_pool

constant_pool_count-1

常量池:可以理解为 Class 文件的资源仓库,表示在ClassFile结构及其子结构中引用的各种字符串常量、类和接口名、字段名和其他常量。后面的其他数据项可以引用常量池内容。

u2

access_flags

1

类的访问标志信息:用于表示这个类或者接口的访问权限及基础属性。

u2

this_class

1

指向当前类的常量索引:用来确定这个类的的全限定名。

u2

super_class

1

指向父类的常量的索引:用来确定这个类的父类的全限定名。

u2

interfaces_count

1

接口的数量

u2

interfaces

interfaces_count

指向接口的常量索引:用来描述这个类实现了哪些接口。

u2

fields_count

1

字段表数量

field_info

fields

fields_count

字段表集合:描述当前类或接口声明的所有字段。

u2

methods_count

1

方法表数量

method_info

methods

methods_count

方法表集合:只描述当前类或接口中声明的方法,不包括从父类或父接口继承的方法。

u2

attributes_count

1

属性表数量

attributes_info

attributes

attributes_count

属性表集合:用于描述某些场景专有的信息,如字节码的指令信息等等。

0 人点赞