2.5 生成字节码
成功经历过词法分析、语法分析和语义分析等步骤之后,所解析出来的语法树已经非常完善了, 那么 javac 编译器最后的任务就是调用 com.sun.tools.javac.jvm.Gen 类将这棵语法树编译为 Java 字节码文件。其实所谓编译字节码,无非就是将符合 Java 语法规范的 Java 代码转换为符合 JVM 规范的字节码文件。在此大家需要注意, JVM 的架构模型是基于栈的(请阅读第 8 章),也就是说,在 JVM 中所有的操作都需要经过入栈和出栈来完成。
2.6 实战:使用javap工具分析字节码
当 javac 编译器成功将代码 2-19 编译为字节码后,便会在源文件的同级目录下生成与源文件名称对应的后缀名为“ .class”的字节码文件。为了更好地理解字节码文件中的内部组成结构,大家可以使用文本编辑器打开字节码文件去一探究竟。本书推荐使用 UltraEdit 编辑器,因为 UltraEdit 不仅使用简单,而且功能强大。
当成功打开字节码文件后, UltraEdit 编辑器便会自动将字节码文件中的内容转换为十六进制数值进行显示。其中前四个字节为 0xCAFEBABE,这 4 个字节所代表的含义就是 magic。简单来说, magic 就是 JVM 用于校验所读取的目标文件是否是一个有效且合法的字节码文件。或许大家会觉得有些奇怪,为什么 JVM 不通过判断文件后缀名的方法来校验字节码文件呢?其实判断文件后缀名也未尝不可,只不过无法确保用户是否会采用手动的方式修改文件的后缀名,所以 Java 的设计者们并没有采用此方案。排列在 magic 后的第 5 个和第 6 个字节所代表的含义就是编译的次版本号,而第 7 个和第 8 个字节就是编译的主版本号。如图 2-8 所示。
如果大家细心的话,应该会发现字节码文件中几乎没有使用任何的分隔符区分段落。参考《Java 虚拟机规范( Java SE7 版)》的描述来看,字节码结构组成比较特殊(请阅读第 3 章),其内部并不包含任何的分隔符区分段落,所以无论是字节顺序、数量都是有严格规定的,所有16 位、 32 位、 64 位长度的数据都将构造成 2 个、 4 个和 8 个 8 位字节单位来表示,多字节数据项总是按照 big-endian 顺序(高位字节在地址最低位,低位字节在地址最高位)来进行存储。也就是说,一组 8 位字节单位的字节流组成了一个完整的字节码文件。
为了更好地分析字节码文件中的内容,大家可以使用 JDK 自带的 javap 工具来反编译字节码文件, 在控制台中输入命令“ javac -help”即可查阅 javap 工具的具体使用方式和一些标准选项配置。
具体用法: javap <选项> <classes> 其中选项包括: -help --help -? 打印此用法信息 -version 版本信息 -v -verbose 打印的附加信息 -l 打印行数和局部变量表 -public 仅显示公共类和成员 -protected 显示受保护的公共类和成员 -package 显示包/保护/公共类和成员(默认) -p –private 显示所有的类和成员 -c 反汇编代码 -s 打印内部类型签名 -sysinfo 显示系统信息(路径,大小,日期, MD5 哈希值)被处理的类 -constants 显示静态常量 -classpath <path> 指定在何处查找用户类文件 -bootclasspath <path> 重写引导类文件的位置为了验证之前语义解析步骤中语义解析器是否会为没有显式定义构造方法的类型动态添加一个无参的缺省构造方法,以及程序中一个 String 类型的变量如果包含多个字符串信息并通过符号“ +”组合在一起时,语义解析器能否会将其合并为一个字符串。那么接下来我们就通过一段简单的 Java 代码来进行验证,如下所示:
代码 2-21 验证语义解析后的变化 /** * 验证语义解析 * * @author JohnGao */ public class DemoTest { /** * @param agrs */ public static void main(String[] agrs) { final String STR = "Java 虚拟机精讲," + "作者:高翔龙"; System.out.println(STR); } }