Android---字节码层面分析Class类文件

Java 提供了一种可以在所有平台上都能使用的一种中间代码---字节码文件(.class文件)。有了字节码,无论是那个平台只要安装了虚拟机都可以直接运行字节码文件。有了虚拟机,解除了 java 虚拟机与 java 代码之间的耦合。

Java 虚拟机当初被设计出来时就不单单只运行 java 这一种语言,目前 java 虚拟机已经可以支持很多除 java 语言以外的其它语言了,比如 Groovy, JRuby, json, skilla等。之所以可以支持其它语言,是因为这些语言经过编译之后,也可以生成能够被 JVM 解析并执行的字节码文件。而虚拟机并不关心字节码是由哪一种语言编译而来,如下图所示:

class 文件

从纵观的角度看,class 文件里只有两种数据结构:无符号数

\bullet 无符号数:属于基本的数据类型。以 u1,u2,u4,u8来分别代表 1 个字节、2个字节、4个字节和8个字节的无符号数。无符号数可以用来描述数字、索引引用,数量值或者字符串(UTF-8编码)。

\bullet 表:表由多个无符号数或者其它表作为数据项构成的复合数据类型。class 文件中所有的表都以“_info”结尾。整个 class 文件本质上就是一张表。

表和无符号数之间的关心

class 文件结构

无符号数和表组成了 class 中的各个结构,这些结构按照预先规定好的顺序紧密的从前向后排列,相邻的项之间没有任何间隙。当 JVM 加载某个 class 文件时,JVM 就是根据上图中的结构去解析 class 文件。加载 class 文件到内存中,并在内存中分配相应的内存空间,具体某种结构需要占用多大的空间,可以参考如下图:

实例解析:

把 test.java 编译成 test.class 文件。用十六进制编辑器打开 .class 文件(可以用在线的编辑器 HexD.it)。

package software_test;import java.io.Serializable;public class test implements Serializable, Cloneable {private int num = 1;public int add(int i) {int j = 10;num = num + i + j;return num;}}

打开 test.class 文件的内容

1. 魔数(magic number)

在 class 文件开头的四个字节是 class 文件的魔数,它是一个固定的值--0XCAFEBABE。魔数是 class 文件的标志,它是判断一个文件是不是 class 格式文件的标准。

2. 版本号

前两个字节 0000代表次版本号(minor_version),后两个字节 0034 是主版本号(major_version),对应的十进制值为52,当前 class 文件的主版本号为52,次版本号为0,所以综合版本号是52.0,也就是 jdk1.8.0。

3. 常量池

紧跟在版本号之后的是一个叫做常量池的表(cp_info)。在常量池中保存了类的各种相关信息,比如类的名称父类的名称类中的方法名参数名称参数类型等。

常量池中的每一项都是一个表,其项目类型共有14种。常量池中的每一项都会有一个 u1 大小的 tag 值,是表的标识。

JVM 解析 class 文件时,通过 tag 值来判断当前数据结构是哪一个表。例如,CONSTANT_Class_info 表:

tag:占用一个字节大小,值为为7,说明是 CONSTANT_Class_info 类型表。

name_index:是一个索引值,可以将它理解为一个指针,指向常量池中索引为 name_index 的常量表。比如 name_index = 7,则它指向常量池中第7个常量表(表与表之间是有关联的)。

再例如 CONSTANT_Utf8_info 表:

tag:值为1,表示是 CONSTANT_Utf8_info类型表。

length:表示 u1[]的长度,比如length = 5,则表示接下来的数据是 5 个连续的u1类型数据。

bytes: u1 类型数组,长度为上面第2个参数 length 的值。

面试题:Java 源文件中 String 字符串的长度 有限制吗?

有(字符串存储在Class文件的常量池中)。在 Java 代码中声明的 String 字符串最终在 class 文件中的存储格式是 CONSTANT_utf8_info因此一个字符串最大长度也就是 u2 所能代表的最大值 65536(2^16) 个,但是需要使用2个来保存null值,因此一个字符串的最大长度为 65536 - 2 = 65534。

class 文件在常量池的前面使用2个字节的容量计数器,用来代表当前类中常量池的大小。

001D 转化为十进制为29,即常量计数器的值为29。其中下标为0的常量被 JVM 留作其他特殊用途,因此 Test.class 中时间的常量池大小为这个计数器的值减1,也就是28个。

第一个常量:

0A 转化为十进制为10,通过查看常量池14种表格图,可以查到 tag=10的表类型为 CONSTANT_Methodref_info,因此常量池中的第一个常量类型为类的方法引用表。其结构如下:

也就是说,0A之后的两个直接是指向该方法所属类,再紧跟的两个字节指向此方法的名称和类型。

0006:十进制为6,表示指向常量池中的第6个常量;

000F:十进制为15,表示指向常量池中的第 15 个常量。

至此,第一个常量解读完毕!

第二个常量:

09转化为十进制为9,即tag = 9,表示是字段引用表 CONSTANT_Fieldref_info,其结构如下:

同理,

0010:指向常量池中第 16 个常量,0011指向常量池中第17个常量。

至此,我们已经解析了常量池中的2个常量,剩下的26个常量也是如此。

4. 访问标志

紧跟在常量池之后的常量是访问标志,占用两个字节。访问标志代表类或者接口的访问信息。比如:该 class 文件是类还是接口,是否被定义成 public,是否是 abstract,如果是类,是否被声明成 final 等。各种访问标志如下图所示:

我们定义的 test.java 是一个普通 Java 类,不是接口、枚举或注解,并且被 public 修饰但没有被声明为 final 和 abstract,因此它所对应的 access_flags 为 0021(0X0001 和 0X0020相结合)。

5. 类索引、父类索引和接口索引计时器

访问标志后的2个字节就是类索引,类索引后的2个字节就是父类索引,父类索引后的2个字节则是接口索引计数器。如下图所示:

 综上所述,我们可以得出当前类为 Test继承自 Object 类,并实现了 “Serializable”和“Cloneable”这两个接口。 

6. 字段表

紧跟在接口索引集合后面的就是字段表,字段表的主要功能是用来描述类或者接口中声明的变量。这里的字段包含了类级别变量以及实例变量,但不包含方法中的局部变量。其具体结构如下:

字段访问标志

其中,第7和8个常量就 num 和 i。因此可以得出,类中有一个为num,类型为 int  的变量。

7. 方法表

字段之后跟着的就是方法表常量,方法表常量应该也是以一个计数器开始的,因为一个类中的方法数量是不固定的。

上图表示 test.class中有两个方法,但是我们只在 test.java 中声明了一个 add 方法,因为默认构造器方法也被包含在方法表常量中。方法表结构如下:

访问标志

8. 属性表

在之前解析字段和方法的时候,在它们的具体结构中,都能看到有一个叫做 attributes_info 的表,这就是属性表。属性表没有一个固定结构,各种不同的属性只要满足以下结构即可:

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/128335.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

zookeeper入门篇之分布式锁

文章目录 前言非公平锁公平锁 前言 上一篇说过,zookeeper是一个类似文件系统的数据结构,每个节点都可以看做是一个文件目录,也就是说,我们所创建的节点是唯一的,那么分布式锁的原理就是基于这个来的。 代码仓库&…

Eclipse iceoryx(千字自传)

1 在固定时间内实现无任何限制的数据传输 在汽车automotive、机器人robotics和游戏gaming等领域,必须在系统的不同部分之间传输大量数据。使用Linux等操作系统时,必须使用进程间通信(IPC)机制传输数据。Eclipse iceoryx是一种中间件,它使用零拷贝Zero-Copy、共享内存Share…

Python 代码调试

from pdb import set_trace as stx 是一个Python代码中常用的调试技巧之一,它用于在代码中插入断点以进行调试。这行代码的作用是将Python标准库中的 pdb(Python Debugger)模块中的 set_trace 函数导入,并将其重命名为 stx&#x…

【重拾C语言】七、指针(一)指针与变量、指针操作、指向指针的指针

目录 前言 七、指针 7.1 指针与变量 7.1.1 指针类型和指针变量 7.1.2 指针所指变量 7.1.3 空指针、无效指针 7.2 指针操作 7.2.1 指针的算术运算 7.2.2 指针的比较 7.2.3 指针的递增和递减 7.3 指向指针的指针 前言 指针是C语言中一个重要的概念正确灵活运用指针 可…

Flink状态管理与检查点机制

1.状态分类 相对于其他流计算框架,Flink 一个比较重要的特性就是其支持有状态计算。即你可以将中间的计算结果进行保存,并提供给后续的计算使用: 具体而言,Flink 又将状态 (State) 分为 Keyed State 与 Operator State: 1.1 算子状态 算子状态 (Operator State):顾名思义…

基于Java的考试报名系统设计与实现(亮点:可修改任意形式的考试报名,如驾校考试报名、竞赛考试报名、英语四级考试报名等)

文章目录 前言具体实现截图论文参考详细视频演示为什么选择我自己的网站自己的小程序(小蔡coding)有保障的售后福利 代码参考源码获取 前言 💗博主介绍:✌全网粉丝10W,CSDN特邀作者、博客专家、CSDN新星计划导师、全栈领域优质创作…

Elasticsearch安装访问

Elasticsearch 是一个开源的、基于 Lucene 的分布式搜索和分析引擎,设计用于云计算环境中,能够实现实时的、可扩展的搜索、分析和探索全文和结构化数据。它具有高度的可扩展性,可以在短时间内搜索和分析大量数据。 Elasticsearch 不仅仅是一个…

基于Java的新能源汽车在线租赁平台设计与实现(源码+lw+ppt+部署文档+视频讲解等)

文章目录 前言具体实现截图论文参考详细视频演示为什么选择我自己的网站自己的小程序(小蔡coding)有保障的售后福利 代码参考源码获取 前言 💗博主介绍:✌全网粉丝10W,CSDN特邀作者、博客专家、CSDN新星计划导师、全栈领域优质创作…

【MySQL】Linux 中 MySQL 环境的安装与卸载

文章目录 Linux 中 MySQL 环境的卸载Linux 中 MySQL 环境的安装 Linux 中 MySQL 环境的卸载 在安装 MySQL 前,我们需要先将系统中以前的环境给卸载掉。 1、查看以前系统中安装的 MySQL rpm -qa | grep mysql2、卸载这些 MySQL rpm -qa | grep mysql | args yum …

时序预测 | MATLAB实现ICEEMDAN-IMPA-GRU时间序列预测

时序预测 | MATLAB实现ICEEMDAN-IMPA-GRU时间序列预测 目录 时序预测 | MATLAB实现ICEEMDAN-IMPA-GRU时间序列预测预测效果基本介绍程序设计参考资料 预测效果 基本介绍 ICEEMDAN-IMPA-GRU功率/风速预测 基于改进的自适应经验模态分解改进海洋捕食者算法门控循环单元时间序列预…

C# 图解教程 第5版 —— 第1章 C# 和 .NET 框架

文章目录 1.1 在 .NET 之前1.2 .NET 时代1.2.1 .NET 框架的组成1.2.2 大大改进的编程环境 1.3 编译成 CIL1.4 编译成本机代码并执行1.5 CLR1.6 CLI1.7 各种缩写1.8 C# 的演化1.9 C# 和 Windows 的演化(*) 1.1 在 .NET 之前 MFC(Microsoft Fou…

UG\NX二次开发 用程序修改“用户默认设置”

文章作者:里海 来源网站:《里海NX二次开发3000例专栏》 感谢粉丝订阅 感谢 wuguoyana、duanxheng 两位粉丝订阅本专栏,非常感谢。 简介 可以用程序修改“用户默认设置”吗?下面是用代码修改“用户默认设置->基本环境->用户界面->操作记录->操作记录语言”的例子…