class文件格式

前言

  • class文件的格式
  • 如何通过jclasslib插件解析class文件

class文件

  • java文件通过javac编译后得到class文件
  • 是二进制字节流

比如一个最简单的java文件如下:

1
2
public class ClassFileFormatT01 {
}

编译完成后得到 ClassFileFormatT01.class 文件 ,可以通过 notepad++ 中的 Hex-Editor 插件查看其16进制格式的内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
ca fe ba be 00 00 00 34 00 10 0a 00 03 00 0d 07 
00 0e 07 00 0f 01 00 06 3c 69 6e 69 74 3e 01 00
03 28 29 56 01 00 04 43 6f 64 65 01 00 0f 4c 69
6e 65 4e 75 6d 62 65 72 54 61 62 6c 65 01 00 12
4c 6f 63 61 6c 56 61 72 69 61 62 6c 65 54 61 62
6c 65 01 00 04 74 68 69 73 01 00 14 4c 43 6c 61
73 73 46 69 6c 65 46 6f 72 6d 61 74 54 30 31 3b
01 00 0a 53 6f 75 72 63 65 46 69 6c 65 01 00 17
43 6c 61 73 73 46 69 6c 65 46 6f 72 6d 61 74 54
30 31 2e 6a 61 76 61 0c 00 04 00 05 01 00 12 43
6c 61 73 73 46 69 6c 65 46 6f 72 6d 61 74 54 30
31 01 00 10 6a 61 76 61 2f 6c 61 6e 67 2f 4f 62
6a 65 63 74 00 21 00 02 00 03 00 00 00 00 00 01
00 01 00 04 00 05 00 01 00 06 00 00 00 2f 00 01
00 01 00 00 00 05 2a b7 00 01 b1 00 00 00 02 00
07 00 00 00 06 00 01 00 00 00 01 00 08 00 00 00
0c 00 01 00 00 00 05 00 09 00 0a 00 00 00 01 00
0b 00 00 00 02 00 0c

接下来我们就来看看这些看似毫无头绪的二进制码代表着什么。

class文件格式

class文件构成

一个class文件由以下部分组成,如下图所示:

class文件格式

  • class文件标志 Magic Number,4字节,固定为 0xcafe babe
  • 版本号
    • Minor Version:2字节,jdk1.8编译出的class文件固定为0x0000
    • MajorVersion:2字节,jdk1.8编译出的class文件固定为0x0034
  • 常量池信息
    • constant_pool_count:2字节,常量池内的常量的数量
    • constant_pool:常量池,由于其保留了index为0的常量,因此常量池里实际的常量池数量为constant_pool_count - 1
  • 访问修饰符 access flags:2字节,用于描述该类是public还是private等等
  • 当前类 this_class:2字节,当前类在常量池中的index,如:本例中等于2,则找到常量池中的2#常量,即可找到ClassFileFormatT01类的信息
  • 父类 super_class:2字节,父类在常量池中的index,如:本例中等于3,则找到常量池中的3#常量,即可找到父类java/lang/Object的信息
  • 接口信息
    • interfaces_count:2字节
    • interfaces:实际数量为 interfaces_count
  • 属性信息
    • fields_count:2字节
    • fields:实际数量为 fields_count
  • 方法信息
    • methods_count:2字节
    • methods:实际数量为 methods_count
  • 附加属性信息
    • attributes_count:2字节
    • attributes:实际数量为 attributes_count

插件jclasslib

上一节只是对class文件的构成做了一个概览性质的描述,实际上还有很多细节,比如:常量池中某一个常量的格式是怎样的?

这些细节就放到文章最后以附录的形式贴上去吧,毕竟,一般也不太会人工一字节一字节的对着二进制码去解析。

我们可以通过 IDEA 中的 jclasslib 插件,方便的解析class文件。

安装完 jclasslib 插件后, 重启 IDEA,编译java文件,将光标放在该java文件中,就可以在 View 菜单下找到 Show Bytecode With jclasslib选项了。

我们通过 jclasslib 分析本文开头编译得到的 ClassFileFormatT01.class 文件,如下图所示:

jclasslib

可以看到在弹出的页面的左侧框中,有 General InformationConstant PoolInterfacesFieldsMethodsAttributes几个大类,右侧框内则是选中条目的详细信息。

比如点击概览信息 General Information,我们能看到该类的jdk编译版本为1.8,常量池数量为16,访问级别为public,类名是ClassFileFormatT01,父类是 java/lang/Object,接口数量和属性数量为0,方法数量为1,附加属性数量为1。

点开ConstanPool,还能看到常量池中每个常量的具体信息。

比如在总览信息中,This classcp_info #2 <ClassFileFormatT01>,就表示2号常量中保存了 This class 的信息,于是我们找到2号常量,如下图所示,可以看到2号常量是一个类型为 CONSTANT_Class_info 的常量,它有一个 Class name 属性,即类名,这个属性的值又指向了14号常量。

2号常量

我们再去看14号常量,可以看到14号常量是一个类型为 CONSTANT_Utf8_Info 的常量,即字符串,字符串的内容就是 ClassFileFormatT01,即类名。

14号常量

此外,还有个值得注意的地方:源码中,我们什么都没写,但是编译出来的class文件中却有1个方法,并且方法的名称是 init,也就是说java帮我们自动生成了一个构造方法。

构造方法

我们再来简单看下这个构造方法的字节码,就3个指令(点击指令,可以直接链接到Oracle官网文档该指令的描述处,非常方便):

  1. 把0号本地变量压栈,0号变量可以在 init 下面的 Code 下面的 LocalVariableTable中找到,就是 this
  2. 调用了一个方法,这个方法要去1号常量里找,实际上就是父类即 java/lang/Object 的构造方法。
  3. 返回

附录:Class文件格式详情

。。。实在是太多了。。。

详见《Java Virtual Machine Specification》的第四章 “4. The class File Format