JVM Bytecode
分析字节码的步骤:
预备好字节码文件参照表,这里有定义好的一些结构,比如字面量、常量池类型和它相关的数据结构、接口相关的数据结构、类相关的等等,可以找官方的 Java Virtual Machine Specification / The class file format.
编译源码得到 class 文件,用 16 进制查看器可以看到二进制内容
根据字节码文件的结构定义,按顺序进行分析,不懂的就查手册
字节码
字节码是 JVM 虚拟机规范的一部分,它是 Java 实现一次编译到处运行的关键,它连接了 Java 源码和机器码,所以掌握字节码对于理解 JVM 虚拟机的运行机制非常有帮助。
在 JVM 的虚拟机规范中有关于字节码类文件的定义,参看 Java Virtual Machine Specification / The class file format. 字节码文件结构是一组以 8 字节为基础的二进制流,各数据严格按照顺序紧凑的排列在文件中,中间并没有特殊的分隔符,如何识别不同的组(或者说不同的部分)?使用的是长度标识的方法。下面是字节码文件结构的大致组成部分:

通过一段程序来分析:
将上面这段代码编译后,会产生 3 个 .class 文件,分别是:BytecodeTest.class, BytecodeReader.class, Reader.class,当我们运行 BytecodeTest.java 中的 main 方法时,JVM 的类加载系统会把这三个字节码文件都加载到内存的方法区(这个区域在 1.8 及以后被叫做元空间 Metaspace),类加载的过程也是读取字节码文件,然后在内存中构建 Java 可使用的对象的过程。
来详细分析一下这些字节码文件,分析最复杂的 BytecodeTest.class 文件,用 16 进制查看器查看这个文件如下:
上面有一张字节码文件的大致组成部分的图,依次来分析
最开始的前 4 个字节,是魔数,用来快速判断这是不是一个 Java 的字节码文件,如果不是直接退出或者报错;
CAFEBABE这 4 个字节是 Java 的魔数紧接着的 4 个字节
0000 0034, 前两个字节是此版本号,后两个字节是主版本号,0034表示是 JDK 1.8接着就是常量池的部分,由常量池个数和常量池表组成
第 9~10 这 2 字节表示常量池中常量的个数,这里是
0036,10 进制是 54,那么常量池表中的数据项就是 53 个,为啥需要 -1,因为常量池中的第0个位置被我们的jvm占用了表示为null 所以我们通过编译出来的常量池索引是从1开始的紧接着后面的就是常量池表了,逐项分析,在这个常量池表中的每一项都以1个字节长度的 tag 来标记常量的类型
常量池表中的第 1 项,
0A表示CONSTANT_Methodref_info, 这种类型的常量后面有 2 字节的类索引 (CONSTANT_Class_info)和 2 字节的名称及类型描述符(CONSTANT_NameAndType_info); 所以00 0E是 14,表示指向常量池中位置是 14 的常量;00 1E是 30, 表示指向常量池中位置是 30 的常量,而且这个常量的类型是NameAndType_info第二个常量 07 00 1F, 07 表示
CONSTANT_Class_info, 接着的 2 个字节指向权限定类名,指向索引 31,索引 31 处其实是31 = Utf8 io/github/shniu/toolbox/BytecodeReader, 它是一个 Utf8 的字面量第三个常量 0A 00 02 00 1E,表示是一个
CONSTANT_Methodref_info, 分别指向索引 2 和 30,也就是指向BytecodeReader的无参构造方法以此类推进行分析即可
常量池后紧接着是访问标志符号,2 个字节,我们的文件里是
00 21,表示是 0x0021 是通过位运算 & 计算出来的,这个 class 的访问权限是ACC_PUBLIC和ACC_SUPER

紧接着是 this class name, 表示当前所属类,2 个字节,值为
00 05,表示索引指向为 5 的常量池中的元素,#5 位置是一个 class,指向了BytecodeTest再接着是 super class name, 表示当前类继承的父类名称,占用 2 个字节,值为
00 0F,表示指向 #15,而这个位置是 Object 类,也就是当前类的父类是 Object再接着是接口的相关信息,2 个字节表示接口数量,后面跟着的是接口表;接口数量是
00 00,说明当前类没有实现接口;如果当前类实现了接口,比如代码中的BytecodeReader类实现了一个接口Reader,那么这里的值就是00 01,紧接着的 2 个字节就是该接口指向的常量池中的位置,为00 07,#7 指向的是Class_info接口信息之后是字段表的信息,它表示的是类变量或者实例变量,但不包括方法的局部变量,接着的 2 个字节是字段的个数,值为
00 02;每个字段表按如下方式组成
`00 1A 00 10 00 11 00 01 00 12 00 00


方法表
00 03 表示有 3 个方法,在我们的类中是 无参构造方法、startup 方法、main 方法
最后是 .class 文件属性:
00 01 00 1D 00 00 00 02 00 1E, 表示有 1(00 01) 个属性,属性指向的位置是 00 1D,也就是 #29,属性的长度是 00 00 00 02 也就是长度为 2,00 1E 就是属性的值,指向 #30

字节码文件内容如下
LineNumberTable属性
LineNumberTable是Code属性中的一个子属性,用来描述java源文件行号与字节码文件偏移量之间的对应关系。当程序运行抛出异常时,异常堆栈中显示出错的行号就是根据这个对应关系来显示的
总结


Reference
最后更新于
这有帮助吗?