字节码文件

一、 class文件

1、一些基本的信息

  1. 魔数 java的字节码文件以cafe babe开头,表明这是一个字节码文件
    在这里插入图片描述
  2. 后边 0000 代表java的大版本号
  3. 0034 是一个16进制的数,10进制就是52,代表java的小版本,java是从45开始算,所以52-45+1=8,表明这是java1.8

2、 编译和反编译文件

  1. 可以在xxx.java文件所在目录使用javac 命令编译该文件,进而会生成一个xxx.class文件,可以携带 -g参数,否则可能会出现 不生成局部变量表的问题。例如下:
      F:\JavaPrograms\myway\study-common-program\study-program\src\main\java\com\jvm\classies>javac -g NewTest.java
  1. 可以在xxx.class 文件所在目录使用 javap 命令反编译字节码文件,可以看到具体内容,而不是上方那种看不懂的内容了,例如下:
      javap -v -p NewTest.class
  1. javac 和javap 都可以 通过在其后最佳 -help来看其他可追加的参数

3、文件版本信息

通过反编译可以看到如下:

		
Classfile /F:/JavaPrograms/myway/study-common-program/study-program/src/main/
java/com/jvm/classies/NewTest.class  //文件位置
  Last modified 2022-7-28; size 712 bytes   //最后修改时间
  MD5 checksum c2be8c9196cc8f644c982fe8572b43eb  //md5
  Compiled from "NewTest.java"  //该class文件由哪个文件编译而来
  
public class com.jvm.classies.NewTest 
  minor version: 0 
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER   //可以看下方表格

访问标志的含义如下:

标志名称标志值含义
ACC_PUBLIC0x0001是否为Public类型
ACC_FINAL0x0010是否被声明为final,只有类可以设置
ACC_SUPER0x0020是否允许使用invokespecial字节码指令的新语义.
ACC_INTERFACE0x0200标志这是一个接口
ACC_ABSTRACT0x0400是否为abstract类型,对于接口或者抽象类来说,次标志值为真,其他类型为假
ACC_SYNTHETIC0x1000标志这个类并非由用户代码产生
ACC_ANNOTATION0x2000标志这是一个注解
ACC_ENUM0x4000标志这是一个枚举

4、常量池

常量池可以理解成Class文件中的资源仓库。主要存放的是两大类常量:字面量(Literal)和符号引用(Symbolic References)。字面量类似于java中的常量概念,如文本字符串,final常量等,而符号引用则属于编译原理方面的概念,包括以下三种:

  • 类和接口的全限定名(Fully Qualified Name)
  • 字段的名称和描述符号(Descriptor)
  • 方法的名称和描述符

不同于C/C++, JVM是在加载Class文件的时候才进行的动态链接,也就是说这些字段和方法符号引用只有在运行期转换后才能获得真正的内存入口地址。当虚拟机运行时,需要从常量池获得对应的符号引用,再在类创建或运行时解析并翻译到具体的内存地址中。

上文说到符号引用,我的理解是 这只是一种字面上用来表示引用的记录格式,它记录了引用关系。
,直接看反编译代码:

用途就是 方法表中在描述方法内容时会使用到这些东西

5、方法表

对类内部的方法描述,在字节码中以表的集合形式表现

  • LineNumberTable: 该属性的作用是描述源码行号与字节码行号(字节码偏移量)之间的对应关系。
  • LocalVariableTable**(局部变量表)**: 该属性的作用是描述帧栈中局部变量与源码中定义的变量之间的关系,start 表示该局部变量在哪一行开始可见,length表示可见行数,Slot代表所在帧栈位置,Name是变量名称,然后是类型签名。
    在这里插入图片描述

二、虚拟机栈

class文件只是描述了一个文件的信息,这些信息干看也不知道干啥用,虚拟机运行时会加载类,根据方法表所表述的内容,结合常量池进行‘翻译’并执行。

1 内存中的栈和堆

可以大致上说:栈是运行时单位,而堆是储存的单位。

栈解决程序运行问题:程序如何执行,如何处理数据。

堆解决数据储存的问题,即数据怎么放,放在那里。

2. 虚拟机栈的基本内容
2.1 Java虚拟机栈是什么

Java虚拟机栈(Java Virtual Machine Stack),早期也叫Java栈。每个线程在创建时都会创建一个虚拟机栈,其内部保存一个个的栈帧(Stack Frame),对应着一次次的Java方法调用,是线程私有的。

2.2 生命周期

生命周期和线程的生命周期是一致的

2.3 作用

主管Java程序的运行,保存方法的局部变量,部分结果,并参与方法的调用和返回。

2.4 栈的特点

栈是一种快速有效的分配储存方式,访问速度仅次于程序计数器。

JVM堆虚拟机栈的操作只有两个:

  1. 每个方法执行,伴随着进栈
  2. 方法执行结束后,伴随着出栈。
2.5 虚拟机栈的组成

在这里插入图片描述

三、栈帧的组成

1 局部变量表

局部变量表的实际数据结构就是一个数组,solt就是指的数组的下标。主要用来存储方法参数,方法体内的局部变量。包括基本数据类型,对象引用,return Address。
在这里插入图片描述

局部变量表是线程私有的数据,不存在数据安全问题。

局部变量表所需的容量大小是在编译期确定下来的,并保存在方法的Code属性的maximum local variables数据项中。在方法运行期间是不会改变局部变量表的大小的。

方法嵌套调用的次数由栈的大小决定。一般来说,栈越大,方法嵌套调用次数越多。对一个函数而言,它的参数和局部变量越多,使得局部变量表膨胀,它的栈帧就越大,以满足方法调用所需传递的信息增大的需求。进而函数调用就会占用更多的栈空间,导致其嵌套调用次数就会减少。

局部变量表中的变量只在当前方法调用中有效。在方法执行时,虚拟机通过使用局部变量表完成参数值到参数变量列表的传递过程。当方法调用结束后,随着方法栈帧的销毁,局部变量表也会随之销毁。

局部变量表中的变量也是重要的垃圾回收根节点,只要被局部变量表中直接或间接引用的对象都不会被回收。
2 关于Slot的理解

Slot是局部变量表中最基本的存储单位

32位以内的类型占1个slot,64位以内的类型占2个slot。值得注意的是byte、short、char 在存储前被转换为int,boolean也被转换为int,0表示false,非0表示true。

每个Slot都会对应一个索引,通过这个索引访问局部变量表中的局部变量值。

当一个实例方法被调用的时候,它的方法参数和方法体内部定义的局部变量将会按照顺序被复制到局部变量表中的每一个slot上

如果需要访问局部变量表中一个64bit的局部变量值时,只需要使用前一个索引即可。(比如:访问long或doub1e类型变量)

如果当前帧是由构造方法或者实例方法创建的,那么该对象引用this将会存放在index为0的slot处,其余的参数按照参数表顺序继续排列。

看下图,非静态方法的局部变量表多一个this
在这里插入图片描述

栈帧中的局部变量表中的槽位是可以重用的,如果一个局部变量过了其作用域,那么在其作用域之后申明的新的局部变就很有可能会复用过期局部变量的槽位,从而达到节省资源的目的。

3.操作数栈

在方法执行过程中,根据字节码指令,往栈中写入数据或者提取数据,即入栈和出栈。主要用于保存计算过程中的中间结果,同时作为计算过程中变量临时的存储空间。

在这里插入图片描述

操作数栈是JVM执行引擎的一个工作区,当一个方法被执行,随着栈帧的创建而创建出来,这个方法的操作数栈是空的。并且操作数栈的深度在编译器就定义好了。可以在方法的code属性中max_stack进行查看。

操作数栈没有局部变量表中的类似的索引,只能通过标准的栈操作进行入栈和出栈的操作。并且32位的类型占据一个栈深度,64位的占据两个栈深度。

如果被调用的方法带有返回值的话,其返回值将会被压入当前栈帧的操作数栈中,并更新PC寄存器中下一条需要执行的字节码指令。

操作数栈中元素的数据类型必须与字节码指令的序列严格匹配,这由编译器在编译器期间进行验证,同时在类加载过程中的类检验阶段的数据流分析阶段要再次验证。

另外,我们说Java虚拟机的解释引擎是基于栈的执行引擎,其中的栈指的就是操作数栈。

4.栈顶缓存技术

由于操作数是存储在内存中的,因此频繁地执行内存读/写操作必然会影响执行速度。为了解决这个问题,HotSpot JVM的设计者们提出了栈顶缓存(Tos,Top-of-Stack Cashing)技术,将栈顶元素全部缓存在物理CPU的寄存器中,以此降低对内存的读/写次数,提升执行引擎的执行效率。

动态链接

每一个栈帧内部都包含一个指向运行时常量池中该栈帧所属方法的引用。包含这个引用的目的就是为了支持当前方法的代码能够实现动态链接(Dynamic Linking)。

在Java源文件被编译到字节码文件中时,所有的变量和方法引用都作为符号引用(Symbolic Reference)保存在class文件的常量池里。比如:描述一个方法调用了另外的其他方法时,就是通过常量池中指向方法的符号引用来表示的,那么动态链接的作用就是为了将这些符号引用转换为调用方法的直接引用。

5.方法返回地址

方法的退出存在两种情景:一是正常执行完成后退出,二是出现未处理的以长,非正常退出。无论哪种退出方式,方法退出后都会返回该方法的调用位置。方法正常退出时,调用者的pc计数器的值作为返回地址,即调用该方法的指令的下一条指令的地址。而通过异常退出的,返回地址是要通过异常表来确定,栈帧中一般不会保存这部分信息。

当一个方法开始执行后,只有两种方式可以退出这个方法:

执行引擎遇到任意一个方法返回的字节码指令(return),会有返回值传递给上层的方法调用者,简称正常完成出口;

    一个方法在正常调用完成之后,究竟需要使用哪一个返回指令,还需要根据方法返回值的实际数据类型而定。
    在字节码指令中,返回指令包含ireturn(当返回值是boolean,byte,char,short和int类型时使用),lreturn(Long类型),freturn(Float类型),dreturn(Double类型),areturn。另外还有一个return指令声明为void的方法,实例初始化方法,类和接口的初始化方法使用。

在方法执行过程中遇到异常(Exception),并且这个异常没有在方法内进行处理,也就是只要在本方法的异常表中没有搜索到匹配的异常处理器,就会导致方法退出,简称异常完成出口。

方法执行过程中,抛出异常时的异常处理,存储在一个异常处理表,方便在发生异常的时候找到处理异常的代码

本质上,方法的退出就是当前栈帧出栈的过程。此时,需要恢复上层方法的局部变量表、操作数栈、将返回值压入调用者栈帧的操作数栈、设置PC寄存器值等,让调用者方法继续执行下去。正常完成出口和异常完成出口的区别在于:通过异常完成出口退出的不会给他的上层调用者产生任何的返回值。

参考

  • https://pdai.tech/md/java/jvm/java-jvm-class.html
  • https://www.zhihu.com/question/366524107/answer/2303904085
  • https://blog.csdn.net/qq_43684005/article/details/119563545
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java虚拟机可以读取字节码文件并将其转换成可执行的代码。字节码文件Java源代码编译后生成的二进制文件,它包含了一系列指令,这些指令被Java虚拟机解释和执行。通过这种方式,Java程序可以在不同的硬件平台和操作系统上运行,实现了"Write Once, Run Anywhere"的目标。 Java虚拟机读取字节码文件的过程可以简单概括为以下几个步骤: 1. 加载:Java虚拟机通过类加载器加载字节码文件,将其转换为运行时的类对象。类加载器负责查找并加载类文件,并将其转换为内存中的类对象。 2. 验证:在加载字节码文件后,Java虚拟机会对字节码文件进行验证,确保其符合Java语言规范和虚拟机规范。验证过程包括对字节码文件的结构、语义和安全性进行检查。 3. 准备:在验证通过后,Java虚拟机会为类变量(静态变量)分配内存,并设置默认初始值。此时,还没有执行任何Java代码。 4. 解析:在准备阶段之后,Java虚拟机会对字节码文件中的符号引用进行解析,将其转换为直接引用。这个过程将类或接口的符号引用解析为实际的内存地址。 5. 初始化:在准备阶段之后,Java虚拟机会执行类的初始化操作,包括执行静态初始化块和静态变量的赋值操作。在这个阶段,Java程序的主方法会被调用,程序开始执行。 通过以上步骤,Java虚拟机可以读取字节码文件并执行其中的指令,实现Java程序的运行。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [Java 进阶:实例详解 Java 虚拟机字节码指令](https://blog.csdn.net/m0_54853420/article/details/126104672)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值