查看原文
其他

走进字节码--实战分析

GeekJunz 极客创享会 2019-07-14


一、准备工作

1、工具准备

winHex : windows 平台16进制文件查看工具
JDK  :    正确安装、配置环境变量(JAVA_HOME、path、classpath等)
javap :   windows 系统中命令行界面 查看字节码命令(以汇编形式展示)

界面效果

1//JDK 信息
2F:\tmp\jvmTmp>java -version
3java version "1.8.0_171"
4Java(TM) SE Runtime Environment (build 1.8.0_171-b11)
5Java HotSpot(TM) 64-Bit Server VM (build 25.171-b11, mixed mode)
6

2、测试代码 Test.java

1public class Test{
2
3    private int m;
4
5    public int inc(){
6        return m + 1;
7    }       
8}

3、编译后 Test.class。(执行命令 javap -v Test.class

1F:\tmp\jvmTmp>javap -v Test.class
2
3Classfile /F:/tmp/jvmTmp/Test.class
4  Last modified 2018-7-18; size 265 bytes
5  MD5 checksum 0d5efc4b65ae7eb6d64f84136ce58ff9
6  Compiled from "Test.java"
7public class Test
8  minor version: 0
9  major version: 52
10  flags: ACC_PUBLIC, ACC_SUPER
11Constant pool:
12   #1 = Methodref          #4.#15         // java/lang/Object."<init>":()V
13   #2 = Fieldref           #3.#16         // Test.m:I
14   #3 = Class              #17            // Test
15   #4 = Class              #18            // java/lang/Object
16   #5 = Utf8               m
17   #6 = Utf8               I
18   #7 = Utf8               <init>
19   #8 = Utf8               ()V
20   #9 = Utf8               Code
21  #10 = Utf8               LineNumberTable
22  #11 = Utf8               inc
23  #12 = Utf8               ()I
24  #13 = Utf8               SourceFile
25  #14 = Utf8               Test.java
26  #15 = NameAndType        #7:#8          // "<init>":()V
27  #16 = NameAndType        #5:#6          // m:I
28  #17 = Utf8               Test
29  #18 = Utf8               java/lang/Object
30{
31  public Test();
32    descriptor: ()V
33    flags: ACC_PUBLIC
34    Code:
35      stack=1, locals=1, args_size=1
36         0: aload_0
37         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
38         4return
39      LineNumberTable:
40        line 10
41
42  public int inc();
43    descriptor: ()I
44    flags: ACC_PUBLIC
45    Code:
46      stack=2, locals=1, args_size=1
47         0: aload_0
48         1: getfield      #2                  // Field m:I
49         4: iconst_1
50         5: iadd
51         6: ireturn
52      LineNumberTable:
53        line 60
54}
55SourceFile: "Test.java"

该命令其他用法参考:

走进字节码之查看工具

二、案例分析

接下来进行完整的class 文件分析,关于class 文件的结构、方法/字段描述符、指令表、常量池结构等都在前述文章进行了详细讲解,因此本文会直接应用,将用到的类型再次演示一遍。详细内容参考前文:

走进字节码专题--常量池 附录

走进字节码--class文件结构

1、首先看一下class 文件的结构

1ClassFile {
2    u4 magic;
3    u2 minor_version;
4    u2 major_version;
5    u2 constant_pool_count;
6    cp_info constant_pool[constant_pool_count-1];
7    u2 access_flags;
8    u2 this_class;
9    u2 super_class;
10    u2 interfaces_count;
11    u2 interfaces[interfaces_count];
12    u2 fields_count;
13    field_info fields[fields_count];
14    u2 methods_count;
15    method_info methods[methods_count];
16    u2 attributes_count;
17    attribute_info attributes[attributes_count];
18}

经过编译的字节码文件是严格按照这个顺序排排列的,且类型之间没有分隔符,类型的区分通过标志位来区分,下面一起来探索。

2、对于编译后生成的 Test.class 文件,通过winHex 软件打开,截图如下:

winHex 界面概述

截图简析

  • 左侧是偏移量(不是内存的实际偏移量)

  • 中间部分为实际的二进制码流转换为十六进制后的值,也就是class 文件的内部表示

  • 右侧将对应的十六进制值,转换为 ASCII 码值(软件的提示吧)

开始分析

字节码案例图

  • class 文件的最开始是 u4 的魔数magic,对应值为截图中最开始桃红色部分:


1// 魔数是固定值,如下
2CA FE BA BE  
  • 紧接着就是 u2 的副版本minor_version、u2 的主版本major_version。对应截图浅蓝色和天蓝色部分。

1//Hex 
200 00 00 34
3
4//对应 javap -v Test 中部分汇编
5minor version: 0
6major version: 52
7
8//即主版本号为52,即JDK1.8. 副版本号为0.
  • 接下来就是常量池的个数,u2 格式,如图中黄色部分标注

1//Hex
200 13   //16+3=19
3
4//即常量池中共有(19 - 1 = 18)项常量
5常量池项是从1 开始的,第0个位置会预留出来,用于表示不指向任何常量项

常量池通用格式第一个值为标志位,表示是哪一种类型的常量。

  • 1、第一个常量如图中内绿色所示:

1//Hex
2000 04 00 0F
3
4>说明如下
50A : 即10, 查表知常量项类型为方法符号引用类型。
600 04 : 表示class 引用,此处表示指向第四个常量项
700 0F : 表示该方法的 名称和类型 引用,此处表示指向第15个常量项
8
9//javap 汇编形式对应结果为
10#1 = Methodref          #4.#15         // java/lang/Object."<init>":()V
  • 2、第二个常量如图中紧接着深绿色所示

1//Hex
209 00 03 00 10
3
4>说明如下
509 : 查表知常量项类型为字段符号引用类型。
600 03 : 表示class 引用,此处表示指向第3个常量项
700 10 : 表示该方法的 名称和类型 引用,此处表示指向第16个常量项
8
9//javap 汇编形式对应结果为
10#2 = Fieldref           #3.#16         // Test.m:
  • 第三个常量

1//Hex
207 00 11 
3
4>说明如下
507 : 常量项类型为 Class 类型。
600 11 : 表示class 引用,此处表示指向第17个常量项
7
8//汇编形式对应结果
9#3 = Class              #17            // Test
  • 第4个常量

1//Hex
207 00 12
3
4>说明如下
507 : 常量项类型为 Class 类型。
600 12 : 表示class 引用,此处表示指向第18个常量项
7
8//汇编形式对应结果
9#4 = Class              #18            // java/lang/Object
  • 第5个常量

1//Hex
201 00 01 6D
3
4>说明如下
501 : 常量项类型为 UTF8_info 类型。即一个字符串
600 01 : 表示字符串长度,即后面紧跟着的一个字节
76D : 该字符串的实际内容,查询ASCII 码表可知为 m
8
9//汇编形式对应结果
10#5 = Utf8               m
  • 第6个常量

1//Hex
201 00 01 49
3
4>说明如下
501 : UTF8_info 类型。即一个字符串
600 01 : 字符串长度,即后面紧跟着的一个字节
749: 该字符串的实际内容,查询ASCII 码表可知为 I
8
9//汇编形式对应结果
10#6 = Utf8               I
  • 第7个常量

1//Hex
201 00 06 369 669 74 3E
3
4>说明如下
501 : UTF8_info 类型。即一个字符串
600 06 : 字符串长度,即后面紧跟着的6个字节
7369 669 74 3E: 该字符串的实际内容,查询ASCII 码表可知为 <init>
8
9//汇编形式对应结果
10#7 = Utf8               <init>
  • 第8个常量

1//Hex
201 00 03 28 29 56
3
4>说明如下
501 : UTF8_info 类型。
600 06 : 字符串长度,即后面紧跟着的3个字节
728 29 56: 字符串的实际内容,查询ASCII 码表可知为 <init>
8
9//汇编形式对应结果
10#8 = Utf8               ()V
  • 第9个常量

1//Hex
201 00 04 43 664 65
3
4>说明如下
501 :  UTF8_info 类型
600 04 : 字符串长度,即后面紧跟着的4个字节
743 664 65: 该字符串的实际内容,查询ASCII 码表可知为 Code
8
9//汇编形式对应结果
10#9 = Utf8               Code
  • 第10个常量

1//Hex
201 00 0469 665 475 662 65 72 54 61 62 665
3
4>说明如下
501 :  UTF8_info 类型
600 0F : 字符串长度,即后面紧跟着的15个字节
7469 665 475 662 65 72 54 61 62 665 : 该字符串的实际内容,查询ASCII 码表可知为 LineNumberTable
8
9//汇编形式对应结果
10#10 = Utf8               LineNumberTable
  • 第11个常量

1//Hex
201 00 03 69 663
3
4>说明如下
501 :  UTF8_info 类型
600 03 : 字符串长度,即后面紧跟着的3个字节
769 663: 该字符串的实际内容,查询ASCII 码表可知为 inc
8
9//汇编形式对应结果
10#11 = Utf8               inc
  • 第12个常量

1//Hex
201 00 03 28 29 49
3
4>说明如下
501 :  UTF8_info 类型
600 03 : 字符串长度,即后面紧跟着的3个字节
728 29 49: 该字符串的实际内容,查询ASCII 码表可知为 ()I
8
9//汇编形式对应结果
10#12 = Utf8               ()I
  • 第13个常量

1//Hex
201 00 053 675 72 63 65 46 69 665
3
4>说明如下
501 :  UTF8_info 类型
600 0A : 字符串长度,即后面紧跟着的10个字节
753 675 72 63 65 46 69 665: 该字符串的实际内容,查询ASCII 码表可知为 SourceFile
8
9//汇编形式对应结果
10#13 = Utf8               SourceFile
  • 第14个常量

1//Hex
201 00 09 54 65 73 74 2661 76 61
3>说明如下
401 :  UTF8_info 类型
500 09 : 字符串长度,即后面紧跟着的9个字节
654 65 73 74 2661 76 61: 该字符串的实际内容,查询ASCII 码表可知为 Test.java
7
8//汇编形式对应结果
9#14 = Utf8               Test.java
  • 第15个常量

1//Hex
2000 07 00 08
3>说明如下
40C :  NameAndType_info 类型
500 07 : 常量池引用,即第 7 项常量,表示名称
600 08: 常量池引用,即第 8 项常量,表示类型
7
8//汇编形式对应结果
9#15 = NameAndType        #7:#8          // "<init>":()V
10
11> 其实就是 Object类的 <init>方法,参数和返回值都为空 ()V
  • 第16个常量

1//Hex
2000 05 00 06
3>说明如下
40C :  NameAndType_info 类型
500 05 : 常量池引用,即第 5 项常量,表示名称
600 06: 常量池引用,即第 6 项常量,表示类型
7
8//汇编形式对应结果
9#16 = NameAndType        #5:#6          // m:I
10
11> 其实就是 Int 类型的字段 m
  • 第17个常量

1//Hex
201 00 04 54 65 73 74
3>说明如下
401 :  UTF8_info 类型
500 04 : 字符串长度,即后面紧跟着的4个字节
654 65 73 74 : 该字符串的实际内容,查询ASCII 码表可知为
7
8//汇编形式对应结果
9#17 = Utf8               Test
  • 第18个常量

1//Hex
201 00 10 6A 61 76 2F 6C 61 6E 67 2F 4F 62 6A 65 63 74
3>说明如下
401 :  UTF8_info 类型
500 10 : 字符串长度,即后面紧跟着的10个字节
66A 61 76 2F 6C 61 6E 67 2F 4F 62 6A 65 63 74 : 该字符串的实际内容,查询ASCII 码表可知为 java/lang/Object
7
8//汇编形式对应结果
9#18 = Utf8               java/lang/Object

分析完了常量池部分,后面紧接着就是 u2 的 access_falgs,如桃红色部分

1//Hex
200 21
3
4>说明
51)类和接口的访问标志共有 8 个,见 class 文件结构一节文章。
62)所有使用到的访问标志 应为真,未使用的置为 假。该类是  public 声明的,因此 ACC_PUBLIC 为真。同时不是final 和 abstract 类型修饰,因此有默认的父类 Object,因此 ACC_SUPER为真。 其他都为false 。
73) 因此访问标志为  0x0001 + 0x0020 = 0x0021
8
9//汇编形式
10flags: ACC_PUBLIC, ACC_SUPER
  • 紧接着就是当前类 this_class 和 父类 super_class

100 03 //this_class, 指向常量池第三个常量项
200 04 //super_class,指向常量池第四个常量项
  • 接口信息

100 00 // 接口数量,此处为0。因此后面的 interfaces[] 就不占任何空间
  • 字段信息

100 01  // 表示有一个变量
2//Hex
300 02 00 05 00 06 00 00
4>说明如下
500 02 : 字段的访问标志,此处为 ACC_PRIVATE 
600 05 : 常量池的引用,即第5个常量项,表示字段名称
700 06 : 常量池的引用,即第6个常量项,表示字段类型
800 00 : 表示字段属性个数,此处为 0。则属性表为空
  • 方法信息

100 02 // 方法个数,即2个
2//Hex_method1
300 01 00 07 00 08 00 01 00 09 00 00 00 100 01 00 01 00 00 00 05 2A B7 00 01 B1 00 00 00 01 00 000 00 00 06 00 01 00 00 00 01
4>说明如下
500 01 :  方法的访问标志,此处为 ACC_PUBLIC 
600 07 : 常量池的引用,即第5个常量项,表示方法名称
700 08 : 常量池的引用,即第6个常量项,表示方法类型
800 01 : 表示方法属性个数,此处为 1。后面跟属性表
900 09 : 常量池引用,表示属性名称
1000 00 00 1D : 属性长度,此处为29,即后面 29 个字节为属性内容
1100 01 00 01 00 00 00 05 2A B7 00 01 B1 00 00 00 01 00 000 00 00 06 00 01 00 00 00 01 : 属性具体内容,即案例图中的绿色对应色条显示内容。也可查表看
12
13//Hex_method2
1400 01 00 0B 000 01 00 09 00 00 00 1F 00 02 00 01 00 00 00 07 2A B4 00 02 04 60 AC 00 00 00 01 00 000 00 00 06 00 01 00 00 00 06
15>说明如下
1600 01 :  方法的访问标志,此处为 ACC_PUBLIC 
1700 0B : 常量池的引用,即第11个常量项,表示方法名称
1800 0C : 常量池的引用,即第12个常量项,表示方法类型
1900 01 : 表示方法属性个数,此处为 1。后面跟属性表
2000 09 : 常量池引用,表示属性名称
2100 00 00 1F : 属性长度,此处为31,即后面 31 个字节为属性内容, 内容见案例图中对应黄色色条部分。
  • 属性信息

1//Hex
200 01 00 000 00 00 02 00 0E
3>说明如下
400 01 : 属性的个数,即1个 
500 0D : 常量池的引用,即第13个常量项,表示属性名
600 00 00 02 : 属性长度,2个字节
700 0E : 常量池的引用,即第14个常量项,表示属性内容

好了,至此整个 Test.class 文件的每一个字节码都进行了分析和说明。结合前述class 文件结构、字段属性、方法描述等,和字节码一一对应,最好可以结合 javap 命令看下汇编形式的结果。相信能够很快理解和掌握字节码文件。

三、总结

  • 本节从头到尾讲述了字节码的结构、分布。案例图将每一部分都通过不同的色块进行了标注,便于大家查看和理解。然后根据每一部分逐个字节进行了分析和说明。只需多看两遍,理解和掌握字节码不是问题。

  • 实际应用中主要是更好地便于理解后续的类加载机制、Java内存模型存储机制。实际开发运维过程由于代码量巨大,字节码文件人工去看工作量会承受不起,会用其他的JVM 工具,如jstack 之类。

  • 最后,希望本文能给你带来帮助!



目前不支持留言功能,有疑问可以公号回复。或加微信 GeekJunz 留言!



END

喜欢随手转发点赞^_^


极客创享会
干货分享、结识更多极客



    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存