字节码文件的组成

字节码文件的组成

  • 字节码文件的组成
  • 1 以正确的姿势打开文件
  • 2 字节码文件的组成
    • 2.1 基本信息
    • 2.2 常量池
    • 2.3 字段
    • 2.4 方法
    • 2.5 属性
  • 3 字节码常用工具
    • 3.1 javap
    • 3.2 jclasslib插件
    • 3.3 Arthas
  • 4 字节码常见指令

字节码文件的组成

1 以正确的姿势打开文件

字节码文件中保存了源代码编译之后的内容,以二进制的方式存储,无法直接用记事本打开阅读。
通过NotePad++使用十六进制插件查看class文件:

在这里插入图片描述

无法解读出文件里包含的内容,推荐使用 jclasslib工具查看字节码文件。

Github地址: https://github.com/ingokegel/jclasslib

在这里插入图片描述

2 字节码文件的组成

字节码文件总共可以分为以下几个部分:

  • 基础信息:魔数、字节码文件对应的Java版本号、访问标识(public final等等)、父类和接口信息
  • 常量池: 保存了字符串常量、类或接口名、字段名,主要在字节码指令中使用
  • 字段: 当前类或接口声明的字段信息
  • 方法: 当前类或接口声明的方法信息,核心内容为方法的字节码指令
  • 属性: 类的属性,比如源码的文件名、内部类的列表等

2.1 基本信息

基本信息包含了jclasslib中能看到的两块内容:

在这里插入图片描述

Magic魔数

每个Java字节码文件的前四个字节是固定的,用16进制表示就是0xcafebabe。文件是无法通过文件扩展名来确定文件类型的,文件扩展名可以随意修改不影响文件的内容。软件会使用文件的头几个字节(文件头)去校验文件的类型,如果软件不支持该种类型就会出错。

比如常见的文件格式校验方式如下:

在这里插入图片描述

Java字节码文件中,将文件头称为magic魔数。Java虚拟机会校验字节码文件的前四个字节是不是0xcafebabe,如果不是,该字节码文件就无法正常使用,Java虚拟机会抛出对应的错误。

主副版本号

主副版本号指的是编译字节码文件时使用的JDK版本号,主版本号用来标识大版本号,JDK1.0-1.1使用了45.0-45.3,JDK1.2是46之后每升级一个大版本就加1;副版本号是当主版本号相同时作为区分不同版本的标识,一般只需要关心主版本号。

1.2之后大版本号计算方法就是 : 主版本号 – 44,比如主版本号52就是JDK8。

在这里插入图片描述

版本号的作用主要是判断当前字节码的版本和运行时的JDK是否兼容。如果使用较低版本的JDK去运行较高版本JDK的字节码文件,无法使用会显示如下错误:

在这里插入图片描述

有两种方案:

  1. 升级JDK版本,将图中使用的JDK6升级至JDK8即可正常运行,容易引发其他的兼容性问题,并且需要大量的测试。
  2. 将第三方依赖的版本号降低或者更换依赖,以满足JDK版本的要求。建议使用这种方案

其他基础信息

其他基础信息包括访问标识、类和接口索引,如下:

在这里插入图片描述

2.2 常量池

字节码文件中常量池的作用:避免相同的内容重复定义,节省空间。

常量池中主要存放两大类常量:字面量(Literal)和符号引用(Symbolic References)。

字面量比较接近于Java语言层面的常量概念,如文本字符串、被声明为final的常量值等。

符号引用则属于编译原理方面的概念,主要包括下面几类常量:

  • 被模块导出或者开放的包(Package)
  • 类和接口的全限定名(Fully Qualified Name)
  • 字段的名称和描述符(Descriptor)
  • 方法的名称和描述符
  • 方法句柄和方法类型(Method Handle、Method Type、Invoke Dynamic)
  • 动态调用点和动态常量(Dynamically-Computed Call Site、Dynamically-Computed Constant)

如下图,常量池中定义了一个字符串,字符串的字面量值为123。

在这里插入图片描述

比如在代码中,编写了两个相同的字符串“我爱北京天安门”,字节码文件甚至将来在内存中使用时其实只需要保存一份,此时就可以将这个字符串以及字符串里边包含的字面量,放入常量池中以达到节省空间的作用。

String str1 = "我爱北京天安门";
String str2 = "我爱北京天安门";

常量池中的数据都有一个编号,编号从1开始。比如“我爱北京天安门”这个字符串,在常量池中的编号就是7。在字段或者字节码指令中通过编号7可以快速的找到这个字符串。

字节码指令中通过编号引用到常量池的过程称之为符号引用

在这里插入图片描述

Java 代码在进行 Javac 编译的时候,并不像C和 C++ 那样有“连接”这一步骤,而是在虚拟机加载Class 文件的时候进行动态连接。

也就是说,在 Class 文件中不会保存各个方法、字段最终在内存中的布局信息,这些字段、方法的符号引用不经过虚拟机在运行期转换的话是无法得到真正的内存入口地址,也就无法直接被虚拟机使用的。

当虚拟机做类加载时,将会从常量池获得对应的符号引用,再在类创建时或运行时解析翻译到具体的内存地址之中。关于类的创建和动态连接的内容,在下一章介绍虚拟机类加载过程时再详细讲解。

常量池中每一项常量都是一个表,最初常量表中共有11种结构各不相同的表结构数据,后来为了更好地支持动态语言调用,额外增加了4种动态语言相关的常量。

为了支持Java模块化系统(Jigsaw),又加人了CONSTANT_Module_info和CONSTANT_Packageinfo两个常量,所以截至JDK13,常量表中分别有17种不同类型的常量。

这 17类表都有一个共同的特点,表结构起始的第一位是个u1类型的标志位(tag,取值见表6-3中标志列),代表着当前常量属于哪种常量类型。

2.3 字段

字段中存放的是当前类或接口声明的字段信息。

Java语言中的“字段”(Field包括类级变量以及实例级变量,但不包括在方法内部声明的局部变量。

可以回忆一下在Java语言中描述一个字段可以包含哪些信息。字段可以包括的修饰符有:

  • 字段的作用域(public、private、protected修饰符)
  • 是实例变量还是类变量(static 修饰符)
  • 可变性(final)
  • 并发可见性(volatile修饰符,是否强制从主内存读写)
  • 可否被序列化(transient修饰符)
  • 字段数据类型(基本类型、对象、数组)
  • 字段名称。

上述这些信息中,各个修饰符都是布尔值,要么有某个修饰符,要么没有,很适合使用标志位来表示。而字段叫做什么名字、字段被定义为什么数据类型,这些都是无法固定的,只能引用常量池中的常量来描述。

如下图中,定义了两个字段a1和a2,这两个字段就会出现在字段这部分内容中。同时还包含字段的名字、描述符(字段的类型)、访问标识(public/private static final等)。

在这里插入图片描述

2.4 方法

Class 文件存储格式中对方法的描述与对字段的描述采用了几乎完全一致的方式,方法表的结构如同字段表一样,依次包括:

  • 访问标志(access_flags)
  • 名称索引(name_index)
  • 描述符索引(descriptor_index)
  • 属性表集合(attributes)

因为 volatile 关键字和 transient关键字不能修饰方法,所以方法表的访问标志中没有了ACC_VOLATILE 标志和 ACC_TRANSIENT 标志。

与之相对,synchronizednativestrictfpabstract关键字可以修饰方法,方法表的访问标志中也相应地增加了ACC_SYNCHRONIZED、ACC_NATIVE、ACC_STRICTFP和ACC_ABSTRACT标志。

这些数据项目的含义也与字段表中的非常类似,仅在访问标志和属性表集合的可选项中有所区别。

字节码中的方法区域是存放字节码指令的核心位置,字节码指令的内容存放在方法的Code属性中。

在这里插入图片描述

通过分析方法的字节码指令,可以清楚地了解一个方法到底是如何执行的。先来看如下案例:

int i = 0;
int j = i + 1;

这段代码编译成字节码指令之后是如下内容:

在这里插入图片描述

要理解这段字节码指令是如何执行的,我们需要先理解两块内存区域:操作数栈和局部变量表。

操作数栈是用来存放临时数据的内容,是一个栈式的结构,先进后出。

局部变量表是存放方法中的局部变量,包含方法的参数、方法中定义的局部变量,在编译期就已经可以确定方法有多少个局部变量。

1、iconst_0,将常量0放入操作数栈。此时栈上只有0。

在这里插入图片描述

2、istore_1会从操作数栈中,将栈顶的元素弹出来,此时0会被弹出,放入局部变量表的1号位置。局部变量表中的1号位置,在编译时就已经确定是局部变量i使用的位置。完成了对局部变量i的赋值操作。

在这里插入图片描述

3、iload_1将局部变量表1号位置的数据放入操作数栈中,此时栈中会放入0。

在这里插入图片描述

4、iconst_1会将常量1放入操作数栈中。

在这里插入图片描述

5、iadd会将操作数栈顶部的两个数据相加,现在操作数栈上有两个数0和1,相加之后结果为1放入操作数栈中,此时栈上只有一个数也就是相加的结果1。

在这里插入图片描述

6、istore_2从操作数栈中将1弹出,并放入局部变量表的2号位置,2号位置是j在使用。完成了对局部变量j的赋值操作。

在这里插入图片描述

7、return语句执行,方法结束并返回。

在这里插入图片描述

同理,同学们可以自行分析下i++和++i的字节码指令执行的步骤。

i++的字节码指令如下,其中iinc 1 by 1指令指的是将局部变量表1号位置增加1,其实就实现了i++的操作。

在这里插入图片描述

而++i只是对两个字节码指令的顺序进行了更改:

在这里插入图片描述

面试题:

问:int i = 0; i = i++; 最终i的值是多少?
答:答案是0,我通过分析字节码指令发现,i++先把0取出来放入临时的操作数栈中,
接下来对i进行加1,i变成了1,最后再将之前保存的临时值0放入i,最后i就变成了0。

2.5 属性

属性主要指的是类的属性,比如源码的文件名、内部类的列表等。

在这里插入图片描述

3 字节码常用工具

3.1 javap

javap是JDK自带的反编译工具,可以通过控制台查看字节码文件的内容。适合在服务器上查看字节码文件内容。

直接输入javap查看所有参数。输入javap -v 字节码文件名称 查看具体的字节码信息。如果jar包需要先使用 jar –xvf 命令解压。

在这里插入图片描述

3.2 jclasslib插件

jclasslib也有Idea插件版本,建议开发时使用Idea插件版本,可以在代码编译之后实时看到字节码文件内容。
安装方式:
1、打开idea的插件页面,搜索jclasslib

在这里插入图片描述

2、选中要查看的源代码文件,选择 视图(View) - Show Bytecode With Jclasslib

在这里插入图片描述

右侧会展示对应源代码编译后的字节码文件内容:

在这里插入图片描述

tips:
1、一定要选择文件再点击视图(view)菜单,否则菜单项不会出现。
2、文件修改后一定要重新编译之后,再点击刷新按钮。

3.3 Arthas

Arthas 是一款线上监控诊断产品,通过全局视角实时查看应用 load、内存、gc、线程的状态信息,并能在不修改应用代码的情况下,对业务问题进行诊断,大大提升线上问题排查效率。

官网:https://arthas.aliyun.com/doc/

Arthas的功能列表如下:

在这里插入图片描述

dump

命令详解:https://arthas.aliyun.com/doc/dump.html

dump命令可以将字节码文件保存到本地,如下将java.lang.String 的字节码文件保存到了/tmp/output目录下:

$ dump -d /tmp/output java.lang.StringHASHCODE  CLASSLOADER  LOCATIONnull                   /tmp/output/java/lang/String.class
Affect(row-cnt:1) cost in 138 ms.

jad

命令详解:https://arthas.aliyun.com/doc/jad.html

jad命令可以将类的字节码文件进行反编译成源代码,用于确认服务器上的字节码文件是否是最新的,如下将demo.MathGame的源代码进行了显示。

$ jad --source-only demo.MathGame
/** Decompiled with CFR 0_132.*/
package demo;import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Random;
import java.util.concurrent.TimeUnit;public class MathGame {private static Random random = new Random();public int illegalArgumentCount = 0;
...

4 字节码常见指令

Java字节码指令集是一组用于描述Java程序操作的指令集合。这些指令被用于在Java虚拟机(JVM)上执行Java程序。以下是一些常见的Java字节码指令:

  1. 常量操作:将常量加载到操作数栈中,如ldc(load constant)、ldc_w(load constant wide)、ldc2_w(load constant 2 words)等。

  2. 加载和存储:用于加载和存储数据到局部变量表和操作数栈中,如iload(load int)、istore(store int)、aload(load reference)、astore(store reference)等。

  3. 运算指令:用于执行基本的算术、逻辑和位运算,如iadd(add int)、isub(subtract int)、imul(multiply int)、idiv(divide int)、iand(and int)、ior(or int)等。

  4. 类型转换:用于执行类型转换操作,如i2l(convert int to long)、l2i(convert long to int)、i2c(convert int to char)等。

  5. 对象操作:用于创建对象、访问对象字段和调用对象方法,如new(create new object)、getfield(get field value of object)、putfield(set field value of object)、invokevirtual(invoke instance method)等。

  6. 控制转移:用于控制程序的执行流程,如goto(unconditional branch)、if_icmpeq(branch if int comparison equal)、tableswitch(switch with index)等。

  7. 异常处理:用于异常处理,如athrow(throw exception)、checkcast(check whether object is of given type)、instanceof(determine if object is of given type)等。

  8. 同步:用于实现同步操作,如monitorenter(enter monitor for object)、monitorexit(exit monitor for object)等。

以上只是一部分Java字节码指令,Java虚拟机规范中包含了更多的指令用于支持Java程序的各种操作。

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

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

相关文章

RPA实战演练UiBot6.0新食堂一楼问卷星(类似于之前的网页表单提交)

要使用RPA(Robotic Process Automation,机器人流程自动化)帮助新食堂进行调查问卷,我们可以结合UiBot 6.0来实施具体的计划。以下是一个大致的实战演练计划: 一、目标与需求分析 明确调查目标:了解新食堂…

jdk17 你还想用ScriptEngineManager来执行js代码?

今天要用java来执行配置表的js代码,用 ScriptEngine javaScriptEngine new ScriptEngineManager().getEngineByName(“javascript”); 一直抛异常:Cannot invoke “javax.script.ScriptEngine.eval(String)” because “javaScriptEngine” is null 网上…

5.Hexo为页面标记标签和类别

Hexo的标签和类别基本上是可以在Hexo中将内容分组的两种方式 如果在网站上有一堆内容,有不同的博客文章 将博客文章分类为不同的类别会很有帮助 用特定的关键词为博客文章标记 如果可以同时分类和标记页面,会使网站用户更轻松地找到他们想要的页面类型 …

文章分享:《二代测序临床报告解读指引》

[摘要] 二代测序(next generation sequencing,NGS)已成为中国临床肿瘤医生常用检测工具,而中国超 90%临床医生需要 NGS 报告解读支持。因此,为提升临床医生 NGS 报告解读能力,特编写…

Traefik的EntryPoints是什么?

在探索 Traefik —— 这款极受欢迎的现代反向代理和负载均衡器时,理解其核心组件是非常重要的。其中,EntryPoints 是 Traefik 中一个关键概念,它直接关系到如何接收和处理进入的网络流量。🔑🚦 1. Traefik 的 EntryPo…

蓝桥杯基础18——第13届省赛真题与代码详解

目录 0.心得体会 1.题目如下 2.代码实现的思路 键值扫描 数码管窗口切换 数码管的动态扫描 继电器工作时L3闪烁,整点时刻L1灯光亮5秒 3.变量列表 定义的常量和数组 功能控制和状态变量 定时器和计数变量 4.代码参考 4.1 头文件 onewire.h ds1302.h 4…

react使用npm i @reduxjs/toolkit react-redux

npm i reduxjs/toolkit react-redux 创建一个 store文件夹,里面创建index.js文件和子模块文件夹 index,js文件写入以下代码 import {configureStore} from reduxjs/toolkit // 导入子模块 import counterReducer from ./modules/one import two from ./modules/tw…

Acwing.1375 奶牛回家(最短路朴素dijkstra)

题目 晚餐时间马上就到了,奶牛们还在各自的牧场中悠闲的散着步。 当农夫约翰摇动铃铛,这些牛就要赶回牛棚去吃晚餐。 在吃晚餐之前,所有奶牛都在自己的牧场之中,有些牧场中可能没有奶牛。 每个牧场都通过一条条道路连接到一个…

蓝桥杯【第15届省赛】Python B组

这题目难度对比历届是相当炸裂的简单了…… A:穿越时空之门 【问题描述】 随着 2024 年的钟声回荡,传说中的时空之门再次敞开。这扇门是一条神秘的通道,它连接着二进制和四进制两个不同的数码领域,等待着勇者们的探索。 在二进制…

一些知识点小细节

当遇到的问题有关逆序输出,可以转换一下思想,就是使用for循环的时候,i的初始化是从数组或者是字符串的最后一个,然后注意设置循环结束的条件,最重要的是不要忘记i--;而不是I; 注意:当要逆序输出…

Day 39:动态规划 LeedCode 62.不同路径 63. 不同路径 II

62. 不同路径 一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为 “Start” )。 机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为 “Finish” )。 问总共有多少条不同的路径&#…

Suno AI

Suno is the latest big name in AI, but what is it? Keep reading to learn everything you need to know about Suno AI, including what it is, what it can do, and how much it costs. Suno AI是一款由Anthropic公司开发的人工智能音乐生成器,它利用先进…