0x30 java_Java Class文件结构:概述

Java Class文件结构:概述

想静下心来读点书,从阅读《JVM specification》[1]开始吧。一直希望一探class文件的内部结构,却无端生出了各种借口成蹉跎岁月。18年岁末是个好日子,Let’s go!

Java的class文件描述了类、接口和模块。众所周知,class文件是一个字节码文件,即按照字节(8 bits)来组织和解读内容。为了表述方便,先定义以下的术语:

u1

1个字节

u2

2个字节

u4

4个字节

表项

通过表格方式描述的项目称作“表项”。比如常量池中有14种常量,每种常量的描述方式(属性)不同,因此每种常量使用不同格式的表项来描述。以_info结尾的词即为一个表项,比如CONSTANT_Utf8_info是Utf8编码的字符串的表项,描述了一个Utf8编码的字符串在常量池中的存储格式。

全限定名

类的全限定名是指包括包名的类名,比如sdut.edu.cn.java.Test即为全限定名。

1 class文件的总体结构

class文件的结构如1[2, p165]所示,包含了10个部分。

类型

名称

数量

备注

u4

magic

1

魔数

u2

minor_version

1

次版本号

u2

major_version

1

主版本号

u2

constant_pool_count

1

常量个数

cp_info

constant_pool

constant_pool_count – 1

常量池表项

u2

access_flags

1

类的访问控制符

u2

this_class

1

当前类的全限定名索引

u2

super_class

1

父类的全限定名索引

u2

interfaces_count

1

接口数量

u2

interfaces

interface_count

接口的全限定名索引列表

u2

fields_count

1

字段数量

field_info

fields

fields_count

字段表表项

u2

methods_count

1

方法数量

method_info

methods

methods_count

方法表表项

u2

attributes_count

1

附加属性数量

attribute_info

attributes

attributes_count

附加属性表表项

表 1: class文件结构

初看起来,class文件的内容多的吓人,但是耐心的想一下,就会释然并觉得是很自然的事情了。下面按照class文件的顺序解读其中的每一个细节(字节),并试图说明class文件为什么要这样设计:让我们一起揣测JVM的设计者当初的“小心思”,也是一件非常有意思的事情。

为了更直观的理解class的文件结构,后面的解读以1为例。

public class Person {

private int age;

public boolean isAdult() {

return age > 18;

}

}

解读class文件结构的主要工具是16进制文件编辑器,在Windows下可以使用ultroedit,editplus等工具,在Linux下可以使用xxd,ghex等。另外,JDK也提供了观察class内部结构的工具:javap,只要执行javap -v Person.class即可:

Classfile /home/subaochen/git/blog/src/java/Person.class

Last modified 2018年12月2日; size 310 bytes

MD5 checksum 3f24365f1557571a81fb3369533d20ee

Compiled from "Person.java"

public class Person

minor version: 0

major version: 55

flags: (0x0021) ACC_PUBLIC, ACC_SUPER

this_class: #3 // Person

super_class: #4 // java/lang/Object

interfaces: 0, fields: 1, methods: 2, attributes: 1

Constant pool:

#1 = Methodref #4.#16 // java/lang/Object."":()V

#2 = Fieldref #3.#17 // Person.age:I

#3 = Class #18 // Person

#4 = Class #19 // java/lang/Object

#5 = Utf8 age

#6 = Utf8 I

#7 = Utf8

#8 = Utf8 ()V

#9 = Utf8 Code

#10 = Utf8 LineNumberTable

#11 = Utf8 isAdult

#12 = Utf8 ()Z

#13 = Utf8 StackMapTable

#14 = Utf8 SourceFile

#15 = Utf8 Person.java

#16 = NameAndType #7:#8 // "":()V

#17 = NameAndType #5:#6 // age:I

#18 = Utf8 Person

#19 = Utf8 java/lang/Object

{

public Person();

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 1: 0

public boolean isAdult();

descriptor: ()Z

flags: (0x0001) ACC_PUBLIC

Code:

stack=2, locals=1, args_size=1

0: aload_0

1: getfield #2 // Field age:I

4: bipush 18

6: if_icmple 13

9: iconst_1

10: goto 14

13: iconst_0

14: ireturn

LineNumberTable:

line 5: 0

StackMapTable: number_of_entries = 2

frame_type = 13 /* same */

frame_type = 64 /* same_locals_1_stack_item */

stack = [ int ]

}

SourceFile: "Person.java"

目前你可能对Javap的输出不甚了了,在后面的讲解中我们会逐步弄清楚其中的每一个细节。

2 魔数

class文件的前4个字符是固定内容的“魔数”,通过ghex观察可见如1所示,class文件的魔数是0xCAFEBABE

1

中文翻译过来是“咖啡宝贝”,这其中的故事可以参考Java之父James Gosling的解释:https://en.wikipedia.org/wiki/Java_class_file#Magic_Number

魔数的作用是表征文件是一个合法的Java类文件,任何不以魔数开头的字节码文件都是非法的class文件,虚拟机将拒绝执行。

98a5e09e2b017408023e9ae7671b1228.png

图 1: class的魔数

3 版本号

魔数之后的4个字节表示“版本号”,其中前两个字节是次版本号(minor version),后两个字节是主版本号(major version)。如2所示,Person.class文件的版本号是0x00000037,即主版本号是0x0037(对应的十进制数是55),次版本号是0x0000(对应的十进制数是0),即Person.class的版本号翻译成十进制为55.0。2列出了JDK定义的主版本号,对照可以看出,Person.class是使用Java SE 11编译而成的。

JDK编译器版本

十六进制主版本号

十进制主版本号

JDK 1.1

0x2D

45

JDK 1.2

0x2E

46

JDK 1.3

0x2F

47

JDK 1.4

0x30

48

Java SE 5

0x301

49

Java SE 6

0x32

50

Java SE 7

0x33

51

Java SE 8

0x34

52

Java SE 9

0x35

53

Java SE 10

0x36

54

Java SE 11

0x37

55

Java SE 12

0x38

56

表 2: JDK的版本号定义

2c4058bef5038abbcd89d1caaf4d27e7.png

图 2: class的版本号

class文件的版本号使用了4个字节来表示,可以表达的最大版本号是65535.65535,可见Java的雄心壮志:当前Java11的版本号是55.0,按照目前的开发速度,Java的版本号可以用到数万年之后。

版本号的作用是表明该class文件是由哪个版本的编译器生成的,因此需要相应版本的java虚拟机来解释执行。显然,高版本的Java虚拟机可以解释执行低版本的class文件,反之则不然。

4 小结

简单的开个头。解读class文件的结构需要一点点耐心,需要一点点的技巧。魔数和版本号是固定长度的,都很容易理解,接下来解读class文件中可能是内容最多但不是最复杂的部分:常量池,TBD。

引用

1Oracle, “JVM specification“.

2周志明, 深入理解Java虚拟机 2 (机械工业出版社, 2018).

d4c39bd54dfbfe627dbd9fe226767b2c.png

0

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值