字节码结构分析

1,586 阅读4分钟

使用 java 的人很多,使用 kotlin 的也不少,不过,对于它们编译后的文件,也就是 class 文件,却很多人望而却步,一是它晦涩难懂,二是它对于实际上的业务开发的帮助没那么常用。但是,它并不是没有用处的,只是你不懂而已。

开始正文。


查看 class 文件

首先,我们得先写个 Java 文件:

public class Test {
    public void sayHello(){
        System.out.println("Hello");
    }
}

然后使用 javac 进行编译。

不怕看不懂,连流程图我都给你们写好:

使用 vim 打开:

vim -b Test.class

怎么回事,怎么变成这样,怎么跟之前看过的 class 文件长得不一样?

没事,我们换成 16 进制看看:

:%!xxd

这样舒服多了。好了,我们开始进行说明。

class 文件结构

首先,我们得先想下,由 java 代码编译成字节码,说明字节码是包含了 java 代码里面的全部内容,既然如此,那么字节码使用什么方式进行存储数据会比较高效?是使用换行符进行区分数据吗?还是使用特殊符号?似乎都不太理想。

而字节码实际上是使用流式进行存储,例如竹子,一节节的,每一节存储不同的数据,每一节的长度也不一定相同。大概是通过这种方式进行存储:

有可能有同学会有疑问,既然是像竹子一样,一节节的,那么怎么确定这一节的长度为多少?

一般会有两种解决方式:

  • 固定的字节数
  • 在这一节的开头使用固定的字节数来表示该节的长度

假如是以上面这种方式进行划分,那么,还可以这样进行说明:

魔数

固定 4 个字节,并且固定为 cafebabe,可能有人会好奇,为什么是 cafebabe?

java 图标和 Google 翻译应该能解决我们这个问题:

版本号

固定 4 个字节,前两个字节为次要版本,后两个字节为主要版本:

次要版本:0000 主要版本:0034,转为十进制为:52

我们来对一下表:

所以,我当前的版本为:Java SE 8.0

常量池

可变长度,由前两个字节决定后续的表的数量。

常量池其实并不是我们 java 代码里面使用 static 修饰的成员变量,而是指这些表:

其中,我抽取 CONSTANT_Utf8_info 表格看看:

  • tag:一个字节,标识为哪个表,基本每个表都有这个标识
  • length:两个字节,表明下面的 bytes 长度
  • bytes[length]:length 个字节,存储真正的数据

讲到到这里,可能有些人会懵。

嗯?我不知道在讲字节码吗?字节码不是以流的方式吗?怎么跟表有联系了,表在数据流上是怎么表示的?

我用一个图简单明了:

访问标志

占两个字符,用来标记当前 class 文件的访问信息,例如:该 class 为接口?类?枚举?有没有使用 public 修饰,有下面这些值:

类/父类/接口:

  • 类:占用两个字节,标识类索引。其实就是标识为常量池的哪个表,如 0x0007,指的是常量池中第七个表
  • 父类:占用两个字节,标识父类索引。作用同上。
  • 接口:可变长度,前面两个字节标识接口索引的个数,剩下的为接口索引。

字段描述集合

可变长度,前面两个字节标识字段表的格式,后续为字段表:

  • access_flags:固定两个字节,字段访问的唯一标识,有以下几种:
  • name_index:固定两个字节,字段名称索引,标识常量池中的表。
  • descriptor_index:固定两个字节,字段描述索引,标识常量池中的表。
  • attributes_count:固定两个字节,标识后续的属性表个数。
  • attributes[attributes_count]:属性表:

这个就不往下说了,都是同个套路,有兴趣可以去查询。

方法描述集合

  • methods_count:固定两个字节,标识后续的方法表个数。
  • methods[methods_count]:方法表:

属性描述集合

  • attributes_count:固定两个字节,标识后续的属性表个数。
  • attributes[attributes_count]:属性表:

只要之前我所讲解的东西大家都懂了的话,后续那些自己依葫芦画瓢去看下就行,也没必要去背,知道整个逻辑结构即可,若有兴趣的话,可以通过该链接仔细查看:文档

是不是觉得,其实字节码也没那么难,只要遵循规律即可。

不过的话,这个 16 进制的字节码主要并不是给开发者看的,我们可以通过 javap 进行反编译,这样查看更清楚。

class 反编译

javap -v Test.class

看,是不是跟我们上面讲的结构很像。由于部分数据没有,就省略了些,如字段描述集合就省略了。