Java字节码
2023-06-03 10:17:32 7 举报
AI智能生成
Java字节码
作者其他创作
大纲/内容
虚拟机规范文档
字节码文件的跨平台性
Java源代码的编译结果是字节码,那么肯定需要有一种编译器能够将Java源码编译为字节码,承担这个重要责任的就是配置在path环境变量中的javac编译器。javac是一种能够将Java源码编译为字节码的前端编译器。
AOT (静态提前编译器,Ahead of Time Compiler)
Java的前端编译器
BAT面试题
Demo
通过字节码指令看代码细节
概述
源代码经过编译器编译之后便会生成一个字节码文件,字节码是一种二进制的类文件,它的内容是JVM的指令,而不像C、C++经由编译器直接生成机器码。
字节码文件是什么?
Java虚拟机的指令由一个字节长度的、代表着某种特定操作含义的操作码(opcode)以及跟随其后的零至多个代表此操作所需参数的操作数(operand)所构成。虚拟机中许多指令并不包含操作数,只有一个操作码。
什么是字节码指令(byte code)?
Notepad++ + HexEditor插件
解读虚拟机解释执行的二进制字节码
虚拟机的基石:Class 文件
任何一个Class文件都对应着唯一一个类或接口的定义信息,但反过来说,Class文件实际上它并不一定以磁盘文件的形式存在。Class文件是一组以8位字节为基础单位的二进制流
class类的本质
下面讲解的案例
class 文件格式
cafe babe
01-魔数:Class文件的标志
紧接着魔数的4个字节存储的是Class文件的版本号。同样也是4个字节。第5个和第6个字节所代表的含义就是编译的副版本\"号minor_version第7个和第8个字节就是编译的主版本号major_version.
Java 的版本号是从45开始的,JDK 1.1之后的每个JDK大版本发布主版本号向上加1。font color=\"#e74f4c\
02-Class文件版本号
在版本号之后,紧跟着的是常量池的数量,以及若干个常量池表项。常量池中常量的数量是不固定的,所以在常量池的入口需要放置一项font color=\"#e74f4c\
其值为0x0016,也就是22。需要注意的是,这实际上只有21项常量。索引为范围是1-21。为什么呢?通常我们写代码时都是从0开始的,但是这里的常量池却是从1开始,因为它把第0项常量空出来了。这是为了满足后面某些指向常量池的索引值的数据在特定情况下需要表达\"不引用任何一个常量池项目”的含义,这种情况可用索引值0来表示。
常量池计数器(constant_pool_count)
constant_pool是一种表结构,以1~ constant_pool_count -1为索引,表明了后面有多少个常量项。常量池主要存放两大类常量:字面量(Literal)和符号引用(Symbolic References)它包含了class文件结构及其子结构中引用的所有字符串常量、类或接口名、字段名和其他常量。常量池中的每一项都具备相同的特征。第1个字节作为类型标记,用于确定该项的格式,这个字节称为tag byte (标记字节、标签字节)
全限定名
简单名称
描述符
虚拟机在加载Class文件时才会进行动态链接,也就是说,class文件中不会保存各个方法和字段的最终内存布局信息,因此,这些字段和方法的符号引用不经过转换是无法直接被虚拟机使用的。当虚拟机运行时,需要从常量池中获得对应的符号引用,再在类加载过程中的解析阶段将其替换为直接引用,并翻译到具体的内存地址中这里说明下符号引用和直接引用的区别与关联:符号引用:符号引用以一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要使用时能无歧义地定位到目标即可。font color=\"#e74f4c\
字面量和符号引用
一个字节一个字节解析
javap -verbose[-v] Demo.class
javap命令
class 可视化工具
解析方式
常量类型和类型
常量池表
03-常量池:存放所有常量
如果是 public final 的类则标记为: ACC_PUBLIC | ACC_FINAL
·使用ACC SUPER可以让类更准确地定位到父类的方法super.method() 现代编译器都会设置并且使用这个标记
访问标识修饰符
04-访问标识
在访问标记后,会指定该类的类别、父类类别以及实现的接口,格式如下:
this_class(类索引)
supur_class (父类索引)
指向常量池索引集合,它提供了一个符号引用到所有已实现的接口由于一个类可以实现多个接口,因此需要以数组形式保存多个接口的索引,表示接口的每个索引也是一个指向常量池的CONSTANT_Class (当然这里就必须是接口,而不是类).
interfaces_count (接口计数器)
interfaces
05-类索引、父类索引、接口索引集合
用于描述接口或类中声明的变量。字段(field)包括类级变量以及实例级变量,但是不包括方法内部、代码块内部声明的局部变量。字段叫什么名字、字段被定义为什么数据类型,这些都是无法固定的,只能引用常量池中的常量来描述。它指向常量池索引集合,它描述了每个字段的完整信息。比如字段的标识符、访问修饰符(public、 private或protected)、是类变量还是实例变量(static修饰符)、是否是常量(final修饰符)等。
字段表概述
fields_count (字段计数器)
作用域(public、 private、protected修饰符)是实例变量还是类变量(static修饰符)可变性(final)并发可见性(volatile修饰符,是否强制从主内存读写)可否序列化(transient修饰符)字段数据类型(基本数据类型、对象、数组)字段名称
一个字段的信息包括下面信息(各个修饰符都是布尔值,要么有,要么没有)
字段表访问标志
根据字段名索引的值查询常量池中的指定索引项即可
字段名索引
描述字段的数据类型,方法的参数列表(数量、类型及顺序)和返回值
描述符索引
属性表集合
字段表结构
fields[] (字段表)
06-字段表集合
在字节码文件中,font color=\"#e74f4c\
注意事项
methods_count(方法计数器)
font color=\"#e74f4c\
methods[] (方法表)
07-方法表集合
方法表集合之后的属性表集合,指的是font color=\"#e74f4c\
attributes_count (属性计数器)
属性的通用格式
java8 一共定义了23种属性
属性类型
属性值部分详解
attributes[] (属性表)
08-属性表集合
图灵课堂-JVM字节码解析笔记
Class 文件结构
直接使用javac xx.java不会生成对应的局部变量表等信息,如果使用javac -g xx.java 就能生产所有的相关信息(IDEA/Eclipse 默认情况下都会生成局部变量表,指令和代码行偏移量映射表信息)
javac -g 操作
javap用法
使用javap指令解析Class文件
Class文件类型结构
Java 虚拟机的指令由一个字节长度(8位)的、代表着某种特定操作含义的数字(称为操作码,Opcode)以及跟随其后的零至多个代表此操作所需参数(称为操作数。 Operands)而构成。由于Java虚拟机采用面向操作数栈而不是寄存器的结构,所以大多数的指令都不包含操作数,只有一个操作码。由于限制了 Java 虚拟机操作码的长度为一个字节(即 0~255),这意味着指令集的操作码总数不可能超过256 条
执行模型
在Java虚拟机的指令集中,大多数的指令都包含了其操作所对应的数据类型信息。例如,iload指令用于从局部变量表中加载int型的数据到操作数栈中,而fload指令加载的则是float类型的数据。
i代表对int类型的数据操作1代表longs代表shortb代表bytec代表charf代表floatd代表double
对于大部分与数据类型相关的字节码指令,它们的操作码助记符中都有特殊的字符来表明专门为哪种数据类型服务:
也有一些指令的助记符中font color=\"#e74f4c\
分配内存的时候,最少也得分配4个字节,局部变量表中的一个槽位是4个字节
字节码与数据类型
1-概述
局部变量压栈指令将给定的局部变量表中的数据压入操作数栈
举例
局部变量压栈指令
常量入栈指令的功能是将常数压入操作数栈,根据数据类型和入栈内容的不同,又可以分为const系列、push系列和ldc指令。
指令const系列
指令push系列
指令ldc系列
示例
常量入栈指令
举例分析
槽位复用
出栈局部变量表指令
2-加载与存储指令
所有的算数指令
比较指令的说明
3-算数指令
宽化类型转换
窄化类型转换(强制类型转化)
4-类型转化指令
new : 它接收一个操作数,为指向常量池的索引,表示要创建的类型,执行完成后,将对象的引用压入栈。
创建类实例指令
newarray:创建基本类型数组anewarray:创建引用类型数组multianewarray:创建多维数组
创建数组的指令
创建指令
访问类字段(static字段,或者称为类变量)的指令: getstatic、putstatic访问类实例字段(非static字段,或者称为实例变量)的指令:getfield、putfield
get***是入栈,而put***是出栈
字段访问指令
数组的主要操作指令有: xastore 和 xaload 指令
把一个数组元素加载到操作数栈的指令: baload、caload、 saload、iaload、laload、faload、daload、 aaload将一个操作数栈的值存储到数组元素中的指令: bastore、 castore、 sastore、iastore、 lastore、fastore、 dastore、 aastore
取数组长度的指令: arraylength :该指令弹出栈顶的数组元素,获取数组的长度,将长度压入栈。
数组操作指令
检查类实例或数组类型的指令:font color=\"#a23c73\
类型检测指令
5-对象创建与访问指令
invokevirtual指令:用于调用对象的实例方法,根据对象的实际类型进行分派(虚方法分派),支持多态。这也是Java语言中最常见的方法分派方式。
font color=\"#a23c73\
invokestatic指令用干调用命名类中的类方法(static方法),这是静态绑定的。
invokedynamic:调用动态绑定的方法,这个是JDK 1.7后新加入的指令。用于在运行时动态解析出调用点限定符所引用的方法,并执行该方法。前面4条调用指令的分派逻辑都固化在java虚拟机内部。而invokedynamic指令的分派逻辑是由用户所设定的引导方法决定的。
方法调用指令
方法调用结束前,需要进行返回。方法返回指令是根据返回值的类型区分的。包括ireturn (当返回值是boolean、 byte、 char、 short和int类型时使用)、Ireturn、 freturn、dreturn和areturn另外还有一条return指令供声明为void的方法、实例初始化方法以及类和接口的类初始化方法使用
如果当前返回的是 synchronized 方法,还会执行一个隐含的 monitorexit 指令,退出临界区
方法返回指令
6-方法调用与返回指令
操作数栈管理指令都是针对于 solt 而言
将一个或两个元素从栈顶弹出,并且直接废弃: pop,pop2复制栈顶一个或两个数值并将复制值或双份的复制值重新压入栈顶: font color=\"#a23c73\
7-操作数栈管理指令
条件跳转指令
后者是栈顶元素,而前者是栈顶下面的元素
比较条件跳转指令
多条件分支跳转指令是专为switch-case语句设计的,主要有tableswitch和lookupswitch
tableswitch:要求多个条件分支值是连续的,它内部只存放起始值和终止值,以及若干个跳转偏移量,通过给定的操作数index,可以立即定位到跳转偏移量位置,因此效率比较高。
lookupswitch:内部font color=\"#e855a4\
多条件分支跳转
无条件跳转
8-控制转义指令
抛出异常指令
异常处理与异常表
9-异常处理指令
一个方法无论是否添加synchronized,无法在字节码中看出区别
方法级的同步
同步代码块
方法内指令指令序列的同步
10-同步控制指令
字节码指令集与解析案例
Java字节码
0 条评论
回复 删除
下一页