本文以 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。
class 文件的产生
*.class
文件是 *.java
文件编译后的形成的中间文件。下面使用一个小例子来看一下 class 文件的结构。
例:我们有以下的源代码文件------ Main.java
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
文件,如果要打开这个文件我们会看到一串一串字符乱码,这些字符是无法直接取阅读的。通过反编译后,就可以得到类似于源码的代码,如下。
java
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 文件结构
java
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 | 属性表集合:用于描述某些场景专有的信息,如字节码的指令信息等等。 |