在讨论JVM的相关知识之前,先学习一下class文件。
每个class文件是由8位字节的流组成,其数据类型有:u1 u2 u4和_info,基本结构如下:
ClassFile {
u4 magic; // 魔法数字,表明当前文件是.class文件,固定0xCAFEBABE
u2 minor_version; // 为Class文件的副版本,现在基本固定为0x0000
u2 major_version; // 为Class文件的主版本,对应jdk发行版本
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]; // 各种属性
}
通过官方文档查看对应版本的class文件格式,主要内容在第4章(The class File Format),本文内容也主要来自该文档
通常编译后的class文件是一个16进制文件,本文以HelloWorld.java文件为例来描述
1字节=8byte,对16进制文件,一个字符可以通过4个byte表示,因此1字节对应2个16进制字符
0xCAFEBABE 对应4个字节,即u4
public class HelloWorld {
public static void main(String[] args) {
String hello = "Hello World";
System.out.println(hello);
}
}
本文演示的java版本是jdk13.0.2(对应的版本号是57,即0x0039),编译后的class文件如下:
cafe babe 0000 0039 0024 0a00 0200 0307
0004 0c00 0500 0601 0010 6a61 7661 2f6c
616e 672f 4f62 6a65 6374 0100 063c 696e
6974 3e01 0003 2829 5608 0008 0100 0b48
656c 6c6f 2057 6f72 6c64 0900 0a00 0b07
000c 0c00 0d00 0e01 0010 6a61 7661 2f6c
616e 672f 5379 7374 656d 0100 036f 7574
0100 154c 6a61 7661 2f69 6f2f 5072 696e
7453 7472 6561 6d3b 0a00 1000 1107 0012
0c00 1300 1401 0013 6a61 7661 2f69 6f2f
5072 696e 7453 7472 6561 6d01 0007 7072
696e 746c 6e01 0015 284c 6a61 7661 2f6c
616e 672f 5374 7269 6e67 3b29 5607 0016
0100 1b70 7269 2f6c 6a77 2f6a 6176 612f
6a76 6d2f 4865 6c6c 6f57 6f72 6c64 0100
0443 6f64 6501 000f 4c69 6e65 4e75 6d62
6572 5461 626c 6501 0012 4c6f 6361 6c56
6172 6961 626c 6554 6162 6c65 0100 0474
6869 7301 001d 4c70 7269 2f6c 6a77 2f6a
6176 612f 6a76 6d2f 4865 6c6c 6f57 6f72
6c64 3b01 0004 6d61 696e 0100 1628 5b4c
6a61 7661 2f6c 616e 672f 5374 7269 6e67
3b29 5601 0004 6172 6773 0100 135b 4c6a
6176 612f 6c61 6e67 2f53 7472 696e 673b
0100 0568 656c 6c6f 0100 124c 6a61 7661
2f6c 616e 672f 5374 7269 6e67 3b01 000a
536f 7572 6365 4669 6c65 0100 0f48 656c
6c6f 576f 726c 642e 6a61 7661 0021 0015
0002 0000 0000 0002 0001 0005 0006 0001
0017 0000 002f 0001 0001 0000 0005 2ab7
0001 b100 0000 0200 1800 0000 0600 0100
0000 0900 1900 0000 0c00 0100 0000 0500
1a00 1b00 0000 0900 1c00 1d00 0100 1700
0000 4700 0200 0200 0000 0b12 074c b200
092b b600 0fb1 0000 0002 0018 0000 000e
0003 0000 000c 0003 000d 000a 000e 0019
0000 0016 0002 0000 000b 001e 001f 0000
0003 0008 0020 0021 0001 0001 0022 0000
0002 0023
class文件解析有多种查看方式,最简单的就是通过javap命令查看
javap -v HelloWorld
主要结果如下
public class pri.ljw.java.jvm.HelloWorld
minor version: 0
major version: 57
flags: (0x0021) ACC_PUBLIC, ACC_SUPER
this_class: #21 // pri/ljw/java/jvm/HelloWorld
super_class: #2 // java/lang/Object
interfaces: 0, fields: 0, methods: 2, attributes: 1
Constant pool:
#1 = Methodref #2.#3 // java/lang/Object."":()V
#2 = Class #4 // java/lang/Object
#3 = NameAndType #5:#6 // "":()V
#4 = Utf8 java/lang/Object
#5 = Utf8 <init>
#6 = Utf8 ()V
#7 = String #8 // Hello World
#8 = Utf8 Hello World
#9 = Fieldref #10.#11 // java/lang/System.out:Ljava/io/PrintStream;
#10 = Class #12 // java/lang/System
#11 = NameAndType #13:#14 // out:Ljava/io/PrintStream;
#12 = Utf8 java/lang/System
#13 = Utf8 out
#14 = Utf8 Ljava/io/PrintStream;
#15 = Methodref #16.#17 // java/io/PrintStream.println:(Ljava/lang/String;)V
#16 = Class #18 // java/io/PrintStream
#17 = NameAndType #19:#20 // println:(Ljava/lang/String;)V
#18 = Utf8 java/io/PrintStream
#19 = Utf8 println
#20 = Utf8 (Ljava/lang/String;)V
#21 = Class #22 // pri/ljw/java/jvm/HelloWorld
#22 = Utf8 pri/ljw/java/jvm/HelloWorld
#23 = Utf8 Code
#24 = Utf8 LineNumberTable
#25 = Utf8 LocalVariableTable
#26 = Utf8 this
#27 = Utf8 Lpri/ljw/java/jvm/HelloWorld;
#28 = Utf8 main
#29 = Utf8 ([Ljava/lang/String;)V
#30 = Utf8 args
#31 = Utf8 [Ljava/lang/String;
#32 = Utf8 hello
#33 = Utf8 Ljava/lang/String;
#34 = Utf8 SourceFile
#35 = Utf8 HelloWorld.java
{
public pri.ljw.java.jvm.HelloWorld();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."":()V
4: return
LineNumberTable:
line 9: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lpri/ljw/java/jvm/HelloWorld;
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: (0x0009) ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=2, args_size=1
0: ldc #7 // String Hello World
2: astore_1
3: getstatic #9 // Field java/lang/System.out:Ljava/io/PrintStream;
6: aload_1
7: invokevirtual #15 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
10: return
LineNumberTable:
line 12: 0
line 13: 3
line 14: 10
LocalVariableTable:
Start Length Slot Name Signature
0 11 0 args [Ljava/lang/String;
3 8 1 hello Ljava/lang/String;
}
SourceFile: "HelloWorld.java"
也可以通过JBE(可直接修改)查看, JClassLib(IDEA插件,上述class文件显示如下)
魔数是用来作为文件标识,class文件固定为0xcafebabe,表示可以被JVM识别处理
文件扩展名是可以被随意修改的,因此需要魔数作为文件标识
通过minor_version(m)和major_version(M)决定class文件版本为M.m
其中major version与jdk版本的关系如下表
jdk版本 | 默认major version | 支持的major version |
---|---|---|
1.0.2 | 45 | 45 |
1.1 | 45 | 45 |
1.2 | 46 | 45 - 46 |
1.3 | 47 | 45 - 46 |
1.4 | 48 | 45 - 46 |
5.0 | 49 | 45 - 46 |
6 | 50 | 45 - 50 |
7 | 51 | 45 - 51 |
8 | 52 | 45 - 52 |
9 | 53 | 45 - 53 |
10 | 54 | 45 - 54 |
11 | 55 | 45 - 55 |
12 | 56 | 45 - 56 |
13 | 57 | 45 - 57 |
minor version的大小则与major version有关
更细微的区别可以参考官方文档查看
Constant Pool常量池,其大小为(constant_pool_count - 1),此处示例constant_pool_count对应的是0x0010,即常量池大小为15(16 - 1),之所以要减1,是因为index_0被预留,没有指向任何内容。
Constant pool内容较为细致,后续会单独开章来说
access_flags表示访问标识符,常见有:
标识名 | 标识值 | 标识含义 | 针对对象 |
---|---|---|---|
ACC_PUBLIC | 0x0001 | public类型 | 所有类型 |
ACC_FINAL | 0x0010 | final类型 | 类 |
ACC_SUPER | 0x0020 | 使用invokespecial语义 | 类和接口 |
ACC_INTERFACE | 0x0200 | 接口类型 | 接口 |
ACC_ABSTRACT | 0x0400 | 抽象类型 | 类和接口 |
ACC_SYNTHETIC | 0x1000 | 由compiler生成 | 所有类型 |
ACC_ANNOTATION | 0x2000 | 注解类型 | 注解 |
ACC_ENUM | 0x4000 | 枚举类型 | 枚举 |
ACC_MODULE | 0x8000 | 模块类型 | 模块 |
access_flags的值是通过按位或运算得到,
- public class,对应的是 0x0001 | 0x0020 = 0x0021,隐性的含有ACC_SUPER
- public interface,对应的是 0x0001 | 0x0200 | 0x0400 = 0x0601,隐性的含有ACC_ABSTRACT
当前类,其值指向constant pool table
父类,其值指向constant poll table
interfaces count表示当前类的接口数量,在类型为class的文件中,为0
interfaces[] 数组大小对应为interfaces count
fields count表示当前类的fields数量,包括类变量和实例变量
fields[] 数组大小为fields count,每个元素结构为field_info
field_info {
u2 access_flags;
u2 name_index;
u2 descriptor_index;
u2 attributes_count;
attribute_info attributes[attributes_count];
}
所有的字符均是ASCII字符
methods count表示当前类的方法数量
methods[] 数组大小为fields count,每个元素结构为method_info
method_info {
u2 access_flags;
u2 name_index;
u2 descriptor_index;
u2 attributes_count;
attribute_info attributes[attributes_count];
}
每个方法描述包括0或多个参数(parameter descriptors)和1个返回类型(return descriptor),结构如下:
MethodDescriptor:
({ParameterDescriptor}) ReturnDescriptor
ParameterDescriptor:
FieldType
ReturnDescriptor:
FieldType
VoidDescriptor
VoidDescriptor:
V
一个示例如下:
Object method(int a, double b, Thread thread) {...}
对应的method descriptor如下
(IDLjava/lang/Thread;)Ljava/lang/Object;
// I --> int a
// D --> double b
// Ljava/lang/Thread; --> Thread thread
// java/lang/Thread --> Thread的className
// Ljava/lang/Object; --> Object
attributes count表示当前类的属性数量,包括类变量和实例变量
attributes[] 数组大小为attributes count,每个元素结构为attribute_info
attribute_info {
u2 attribute_name_index;
u4 attribute_length;
u1 info[attribute_length];
}
以上就是class文件格式的主要内容,当然描述的都是相对表象的,下文(class文件2-字节码阅读)通过阅读class文件的形式来说明常量池结构