前言
- class文件的格式
- 如何通过jclasslib插件解析class文件
class文件
- java文件通过javac编译后得到class文件
- 是二进制字节流
比如一个最简单的java文件如下:1
2public class ClassFileFormatT01 {
}
编译完成后得到 ClassFileFormatT01.class 文件 ,可以通过 notepad++ 中的 Hex-Editor 插件查看其16进制格式的内容:
1 | ca fe ba be 00 00 00 34 00 10 0a 00 03 00 0d 07 |
接下来我们就来看看这些看似毫无头绪的二进制码代表着什么。
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 文件,如下图所示:
可以看到在弹出的页面的左侧框中,有 General Information、Constant Pool、Interfaces、Fields、Methods、Attributes几个大类,右侧框内则是选中条目的详细信息。
比如点击概览信息 General Information,我们能看到该类的jdk编译版本为1.8,常量池数量为16,访问级别为public,类名是ClassFileFormatT01,父类是 java/lang/Object,接口数量和属性数量为0,方法数量为1,附加属性数量为1。
点开ConstanPool,还能看到常量池中每个常量的具体信息。
比如在总览信息中,This class 是 cp_info #2 <ClassFileFormatT01>
,就表示2号常量中保存了 This class 的信息,于是我们找到2号常量,如下图所示,可以看到2号常量是一个类型为 CONSTANT_Class_info 的常量,它有一个 Class name 属性,即类名,这个属性的值又指向了14号常量。
我们再去看14号常量,可以看到14号常量是一个类型为 CONSTANT_Utf8_Info 的常量,即字符串,字符串的内容就是 ClassFileFormatT01,即类名。
此外,还有个值得注意的地方:源码中,我们什么都没写,但是编译出来的class文件中却有1个方法,并且方法的名称是 init,也就是说java帮我们自动生成了一个构造方法。
我们再来简单看下这个构造方法的字节码,就3个指令(点击指令,可以直接链接到Oracle官网文档该指令的描述处,非常方便):
- 把0号本地变量压栈,0号变量可以在 init 下面的 Code 下面的 LocalVariableTable中找到,就是 this
- 调用了一个方法,这个方法要去1号常量里找,实际上就是父类即 java/lang/Object 的构造方法。
- 返回
附录:Class文件格式详情
。。。实在是太多了。。。
详见《Java Virtual Machine Specification》的第四章 “4. The class File Format”