本文以 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
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"
补充:反编译工具
javap
。- IDE 工具:如 Idea 工具栏中 View---'show bytecode'。
- 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];
}
案例中的数据类型表示为 u1
、u2
和 u4
类型分别代表一个、两个或四个字节的无符号量,这些类型可通过 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 | 属性表集合:用于描述某些场景专有的信息,如字节码的指令信息等等。 |