likes
comments
collection
share

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

作者站长头像
站长
· 阅读数 5

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

JDK 文档官网:docs.oracle.com/javase/8/

前言

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。 JVM第一课:如何阅读Class文件

class 文件的产生

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

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

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 文件,如果要打开这个文件我们会看到一串一串字符乱码,这些字符是无法直接取阅读的。通过反编译后,就可以得到类似于源码的代码,如下。

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 文件格式的主次版本号、常量池等信息。

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

class 文件结构介绍

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

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

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

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 文件结构的具体含义。

类型名称数量说明
u4magic1魔数:确定一个文件是否是 Class 文件。值是固定的为 0xCAFEBABE
u2minor_version1Class 文件的次版本号,与主版本号共同决定 class 文件格式的版本。如果一个类文件的主要版本号为 M,次要版本号为 m,我们就用 M.m 表示其类文件格式的版本。
u2major_version1Class 文件的主版本号:一个 JVM 实例只能支持特定范围内版本号的 Class 文件(可以向下兼容)。
u2constant_pool_count1常量表数量,constant_pool_count 项的值等于 constant_pool 表中的条目数加1。如果一个常量大于0且小于 constant_pool_count 则被认为是有效的。
cp_infoconstant_poolconstant_pool_count-1常量池:可以理解为 Class 文件的资源仓库,表示在ClassFile结构及其子结构中引用的各种字符串常量、类和接口名、字段名和其他常量。后面的其他数据项可以引用常量池内容。
u2access_flags1类的访问标志信息:用于表示这个类或者接口的访问权限及基础属性。
u2this_class1指向当前类的常量索引:用来确定这个类的的全限定名。
u2super_class1指向父类的常量的索引:用来确定这个类的父类的全限定名。
u2interfaces_count1接口的数量
u2interfacesinterfaces_count指向接口的常量索引:用来描述这个类实现了哪些接口。
u2fields_count1字段表数量
field_infofieldsfields_count字段表集合:描述当前类或接口声明的所有字段。
u2methods_count1方法表数量
method_infomethodsmethods_count方法表集合:只描述当前类或接口中声明的方法,不包括从父类或父接口继承的方法。
u2attributes_count1属性表数量
attributes_infoattributesattributes_count属性表集合:用于描述某些场景专有的信息,如字节码的指令信息等等。