第2章介绍了Java虚拟机从哪里搜索class文件,并且实现了类路径功能,已经可以把class文件读取到内存中。本章将详细讨论class文件格式,编写代码解析class文件,为下一步真正实现Java虚拟机做好准备。
在开始阅读本章之前,先把目录结构准备好。复制ch02目录,并改名为ch03,然后编辑ch03\main.go等文件,把import语句中的ch02都改成ch03,最后在ch03目录中创建classfile子目录。现在的目录结构看起来应该如下所示:
D:\go\workspace\src —-jvmgo —-ch01 —-ch02 —-ch03 —-classfile —-classpath —-cmd.go —-main.go
3.1 class文件
作为类(或者接口)信息的载体,每个class文件都完整地定义了一个类。为了使Java程序可以“编写一次,处处运行”, Java虚拟机规范对class文件格式进行了严格的规定。但是另一方面,对于从哪里加载class文件,给了足够多的自由。由第2章可知,Java虚拟机实现可以从文件系统读取和从JAR(或ZIP)压缩包中提取class文件。除此之外,也可以通过网络下载、从数据库加载,甚至是在运行中直接生成class文件。Java虚拟机规范(和本书)中所指的class文件,并非特指位于磁盘中的.class文件,而是泛指任何格式符合规范的class数据。
构成class文件的基本数据单位是字节,可以把整个class文件当成一个字节流来处理。稍大一些的数据由连续多个字节构成,这些数据在class文件中以大端(big-endian)方式存储。为了描述class文件格式,Java虚拟机规范定义了u1、u2和u4三种数据类型来表示1、2和4字节无符号整数,分别对应Go语言的uint8、uint16和uint32类型。相同类型的多条数据一般按表(table)的形式存储在class文件中。表由表头和表项(item)构成,表头是u2或u4整数。假设表头是n,后面就紧跟着n个表项数据。
Java虚拟机规范使用一种类似C语言的结构体语法来描述class文件格式。整个class文件被描述为一个ClassFile结构,代码如下:
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]; }
JDK提供了一个功能强大的命令行工具javap,可以用它反编译class文件。不过从控制台观察javap的输出并不是很直观,因此笔者用JavaFX编写了一个图形化的工具,叫作classpy。有兴趣的读者可以去GitHub网站下载classpy的源代码或者打包好的JAR可执行文件。后面的小节中将以ClassFileTest类为例,使用classpy程序分析class文件格式。ClassFileTest的代码如下:
package jvmgo.book.ch03; public class ClassFileTest { public static final boolean FLAG = true; public static final byte BYTE = 123; public static final char X = ' X' ; public static final short SHORT = 12345; public static final int INT = 123456789; public static final long LONG = 12345678901L; public static final float PI = 3.14f; public static final double E = 2.71828; public static void main(String[] args) throws RuntimeException { System.out.println("Hello, World! "); } }