字节码文件详解

前言

我们都知道,Java程序最终是转换成class文件执行在虚拟机上的,那么class文件是个怎样的结构,虚拟机又是如何处理去执行class文件里面的内容呢,这篇文章带你深入理解Java字节码中的结构。

1.Demo源码

首先,编写一个简单的Java源码:

package com.april.test;

public class Demo {
   
    private int num = 1;

    public int add() {
   
        num = num + 2;
        return num;
    }
}
12345678910

这段代码很简单,只有一个成员变量num和一个方法add()

2.字节码

要运行一段Java源码,必须先将源码转换为class文件,class文件就是编译器编译之后供虚拟机解释执行的二进制字节码文件,可以通过IDE工具或者命令行去将源码编译成class文件。这里我们使用命令行去操作,运行下面命令:

javac Demo.java
1

就会生成一个Demo.class文件。

我们打开这个Demo.class文件看下。这里用到的是Notepad++,需要安装一个HEX-Editor插件。
1.字节码-完整版.png

3.class文件反编译java文件

在分析class文件之前,我们先来看下将这个Demo.class反编译回Demo.java的结果,如下图所示:
源码与class转java对比.png
可以看到,回编译的源码比编写的代码多了一个空的构造函数this关键字,为什么呢?先放下这个疑问,看完这篇分析,相信你就知道答案了。

4.字节码结构

从上面的字节码文件中我们可以看到,里面就是一堆的16进制字节。那么该如何解读呢?别急,我们先来看一张表:

类型 名称 说明 长度
u4 magic 魔数,识别Class文件格式 4个字节
u2 minor_version 副版本号 2个字节
u2 major_version 主版本号 2个字节
u2 constant_pool_count 常量池计算器 2个字节
cp_info constant_pool 常量池 n个字节
u2 access_flags 访问标志 2个字节
u2 this_class 类索引 2个字节
u2 super_class 父类索引 2个字节
u2 interfaces_count 接口计数器 2个字节
u2 interfaces 接口索引集合 2个字节
u2 fields_count 字段个数 2个字节
field_info fields 字段集合 n个字节
u2 methods_count 方法计数器 2个字节
method_info methods 方法集合 n个字节
u2 attributes_count 附加属性计数器 2个字节
attribute_info attributes 附加属性集合 n个字节

这是一张Java字节码总的结构表,我们按照上面的顺序逐一进行解读就可以了。

首先,我们来说明一下:class文件只有两种数据类型:无符号数。如下表所示:

数据类型 定义 说明
无符号数 无符号数可以用来描述数字、索引引用、数量值或按照utf-8编码构成的字符串值。 其中无符号数属于基本的数据类型。 以u1、u2、u4、u8来分别代表1个字节、2个字节、4个字节和8个字节
表是由多个无符号数或其他表构成的复合数据结构。 所有的表都以“_info”结尾。 由于表没有固定长度,所以通常会在其前面加上个数说明。

实际上整个class文件就是一张表,其结构就是上面的表一了。

那么我们现在再来看表一中的类型那一列,也就很简单了:

类型 说明 长度
u1 1个字节 1
u2 2个字节 2
u4 4个字节 4
u8 8个字节 8
cp_info 常量表 n
field_info 字段表 n
method_info 方法表 n
attribute_info 属性表 n

上面各种具体的表的数据结构后面会详细说明,这里暂且不表。

好了,现在我们开始对那一堆的16进制进行解读。
b.jpg

4.1 魔数

从上面的总的结构图中可以看到,开头的4个字节表示的是魔数,其值为:
2.字节码-魔数.png
嗯,其值为0XCAFE BABE。CAFE BABE??What the fxxk?
好了,那么什么是魔数呢?魔数就是用来区分文件类型的一种标志,一般都是用文件的前几个字节来表示。比如0XCAFE BABE表示的是class文件,那么为什么不是用文件名后缀来进行判断呢?因为文件名后缀容易被修改啊,所以为了保证文件的安全性,将文件类型写在文件内部可以保证不被篡改。
再来说说为什么class文件用的是CAFE BABE呢,看到这个大概你就懂了。
java.jpg

4.2 版本号

紧跟着魔数后面的4位就是版本号了,同样也是4个字节,其中前2个字节表示副版本号,后2个字节
表示主版本号。再来看看我们Demo字节码中的值:
3.字节码-版本号.png
前面两个字节是0x0000,也就是其值为0;
后面两个字节是0x0034,也就是其值为52.
所以上面的代码就是52.0版本来编译的,也就是jdk1.8.0

4.3 常量池

4.3.1 常量池容量计数器

接下来就是常量池了。由于常量池的数量不固定,时长时短,所以需要放置两个字节来表示常量池容量计数值。Demo的值为:
4.字节码-常量池容量计数值.png
其值为0x0013,掐指一算,也就是19。
需要注意的是,这实际上只有18项常量。为什么呢?

通常我们写代码时都是从0开始的,但是这里的常量池却是从1开始,因为它把第0项常量空出来了。这是为了在于满足后面某些指向常量池的索引值的数据在特定情况下需要表达“不引用任何一个常量池项目”的含义,这种情况可用索引值0来表示。

Class文件中只有常量池的容量计数是从1开始的,对于其他集合类型,包括接口索引集合、字段表集合、方法表集合等的容量计数都与一般习惯相同,是从0开始的。

4.3.2 字面量和符号引用

在对这些常量解读前,我们需要搞清楚几个概念。
常量池主要存放两大类常量:字面量符号引用。如下表:

常量 具体的常量
字面量 文本字符串
声明为final的常量值
符号引用 类和接口的全限定名
字段的名称和描述符
方法的名称和描述符
4.3.2.1 全限定名

com/april/test/Demo这个就是类的全限定名,仅仅是把包名的".“替换成”/",为了使连续的多个全限定名之间不产生混淆,在使用时最后一般会加入一个“;”表示全限定名结束。

4.3.2.2 简单名称

简单名称是指没有类型和参数修饰的方法或者字段名称,上面例子中的类的add()方法和num字段的简单名称分别是addnum

4.3.2.3 描述符

描述符的作用是用来描述字段的数据类型、方法的参数列表(包括数量、类型以及顺序)和返回值。根据描述符规则,基本数据类型(byte、char、double、float、int、long、short、boolean)以及代表无返回值的void类型都用一个大写字符来表示,而对象类型则用字符L加对象的全限定名来表示,详见下表:

标志符 含义
B 基本数据类型byte
C 基本数据类型char
D 基本数据类型double
F 基本数据类型float
I 基本数据类型int
J 基本数据类型long
S 基本数据类型short
Z 基本数据类型boolean
V 基本数据类型void
L 对象类型,如Ljava/lang/Object

对于数组类型,每一维度将使用一个前置的[字符来描述,如一个定义为java.lang.String[][]类型的二维数组,将被记录为:[[Ljava/lang/String;,,一个整型数组int[]被记录为[I

用描述符来描述方法时,按照先参数列表,后返回值的顺序描述,参数列表按照参数的严格顺序放在一组小括号“( )”之内。如方法java.lang.String toString()的描述符为( ) LJava/lang/String;,方法int abc(int[] x, int y)的描述符为([II) I

4.3.3 常量类型和结构

常量池中的每一项都是一个表,其项目类型共有14种,如下表格所示:

类型 标志 描述
CONSTANT_utf8_info 1 UTF-8编码的字符串
CONSTANT_Integer_info 3 整形字面量
CONSTANT_Float_info 4 浮点型字面量
CONSTANT_Long_info 5 长整型字面量
CONSTANT_Double_info 6 双精度浮点型字面量
CONSTANT_Class_info 7 类或接口的符号引用
CONSTANT_String_info 8 字符串类型字面量
CONSTANT_Fieldref_info 9 字段的符号引用
CONSTANT_Methodref_info 10 类中方法的符号引用
CONSTANT_InterfaceMethodref_info 11 接口中方法的符号引用
CONSTANT_NameAndType_info 12 字段或方法的符号引用
CONSTANT_MethodHandle_info 15 表示方法句柄
CONSTANT_MothodType_info 16 标志方法类型
CONSTANT_InvokeDynamic_info 18 表示一个动态方法调用点

这14种类型的结构各不相同,如下表格所示:
常量池中常量项的结构总表.png

注:此表格的类型的单位不对,不是bit,应该是byte(字节)。后面的同理。

从上面的表格可以看到,虽然每一项的结构都各不相同,但是他们有个共同点,就是每一项的第一个字节都是一个标志位,标识这一项是哪种类型的常量。

4.3.4 常量解读

好了,我们进入这18项常量的解读,首先是第一个常量,看下它的标志位是啥:
5.字节码-第一个常量的标志位.png
其值为0x0a,即10,查上面的表格可知,其对应的项目类型为CONSTANT_Methodref_info,即类中方法的符号引用。其结构为:
CONSTANT_Methodref_info的结构.png
即后面4个字节都是它的内容,分别为两个索引项:
6.字节码-第一个常量的项目.png
其中前两位的值为0x0004,即4,指向常量池第4项的索引;
后两位的值为0x000f,即15,指向常量池第15项的索引。
至此,第一个常量就解读完毕了。
我们再来看下第二个常量:
7.字节码-第二个常量.png
其标志位的值为0x09,即9,查上面的表格可知

  • 15
    点赞
  • 37
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值