首页 > 科技 > 2019BAT高频面试题解析:Java虚拟机(JVM)的工作方式

2019BAT高频面试题解析:Java虚拟机(JVM)的工作方式

介绍

在诸如C和C ++的高级编程语言中,我们以人类可读的格式编写程序,然后称为编译器的程序将其转换为计算机可以理解和执行的称为可执行代码的二进制格式。可执行代码取决于我们用来执行程序的计算机。它取决于机器。在Java中,这种执行程序的编写过程非常相似,但是有一个重要的区别,那就是使我们可以编写与机器无关的Java程序。

使用解释器,所有Java程序都被编译到称为字节码的中间级别。我们可以在装有Java运行时环境的任何计算机上运行编译后的字节码。运行时环境由虚拟机及其支持代码组成。

JVM是一种仿真

创建Java字节码的困难之处在于,源代码是为不存在的机器编译的。该计算机称为Java虚拟机,仅存在于我们计算机的内存中。愚弄Java编译器为不存在的机器创建字节代码只是使Java体系结构中立的巧妙过程的一半。Java解释器还必须使我们的计算机和字节代码文件相信它们在真实计算机上运行。它通过充当虚拟机和我们的真实计算机之间的中介来做到这一点。(请参见下图。)


图1-在物理机上运行的JVM仿真

Java虚拟机负责解释Java字节码并将其转换为操作或操作系统调用。例如,建立与远程计算机的套接字连接的请求将涉及操作系统调用。不同的操作系统以不同的方式处理套接字-但程序员无需担心此类细节。JVM负责处理这些转换,以使运行Java软件的操作系统和CPU体系结构与开发人员完全无关。(请参见下图。)

图2-JVM处理翻译

Java虚拟机的基本部分

在我们的计算机内存中创建虚拟机需要构建真实计算机的每个主要功能,直到程序运行所在的环境。这些功能可以分为七个基本部分:

  • 一组寄存器
  • 一叠
  • 执行环境
  • 垃圾收集堆
  • 恒定池
  • 方法存储区
  • 指令集

寄存器

寄存器 Java虚拟机的类似于在我们的电脑寄存器。但是,由于虚拟机是基于堆栈的,因此其寄存器不用于传递或接收参数。在Java中,寄存器保存机器的状态,并在执行每一行字节代码后进行更新以保持该状态。以下四个寄存器保存虚拟机的状态:

  • frame,参考框架,并包含指向当前方法的执行环境的指针。
  • optop,操作数顶部,并包含一个指向操作数堆栈顶部的指针,用于评估算术表达式。
  • pc是程序计数器,包含要执行的下一个字节代码的地址。
  • vars,变量寄存器,并包含一个指向局部变量的指针。

所有这些寄存器均为32位宽,并立即分配。这是可能的,因为编译器知道局部变量和操作数堆栈的大小,并且解释器知道执行环境的大小。

堆栈

Java虚拟机使用操作数堆栈为方法和操作提供参数,并从方法和操作接收结果。所有字节码指令均从堆栈中获取操作数,对其进行操作,然后将结果返回至堆栈。像虚拟机中的寄存器一样,操作数堆栈为32位宽。

操作数堆栈遵循后进先出(LIFO)方法,并期望堆栈上的操作数具有特定顺序。例如,isub字节码指令期望将两个整数存储在堆栈的顶部,这意味着操作数必须已被上一组指令压入了那里。isub将操作数从堆栈中弹出,相减,然后将结果推回堆栈。

在Java中,整数是原始数据类型。每个原始数据类型都有唯一的指令,该指令告诉它如何对该类型的操作数进行操作。例如,lsub字节码用于执行长整数减法,fsub字节码用于执行浮点减法,dsub字节码用于执行长整数减法。因此,将两个整数压入堆栈,然后将它们视为单个长整数是非法的。但是,将64位长的整数压入堆栈并占用两个32位插槽是合法的。

Java程序中的每个方法都有一个与之关联的堆栈框架。的堆栈帧保存与三组数据的方法的状态:该方法的局部变量,所述方法的执行环境,并且该方法的操作数栈。尽管局部变量和执行环境数据集的大小总是在方法调用开始时固定的,但是操作数堆栈的大小会随着执行方法的字节码指令而改变。由于Java堆栈宽32位,因此不能保证64位数字是64位对齐的。

执行环境

所述执行环境保持堆栈作为数据集之内,并且是用来处理动态链接,正常方法返回,和异常产生。为了处理动态链接,执行环境包含对当前方法和当前类的方法和变量的符号引用。通过动态链接到符号表将这些符号调用转换为实际的方法调用。

只要方法正常完成,就会向调用方法返回一个值。执行环境通过恢复调用方的寄存器并增加调用方的程序计数器以跳过方法调用指令来处理正常的方法返回。然后在调用方法的执行环境中继续执行程序。

如果当前方法的执行正常完成,则将值返回给调用方法。当调用方法执行适合于返回类型的返回指令时,会发生这种情况。

如果调用方法执行了不适合返回类型的返回指令,则该方法将引发异常或错误。可能发生的错误包括动态链接失败(例如,找不到类文件)或运行时错误(例如,在数组范围之外的引用)。发生错误时,执行环境将生成异常。

垃圾收集堆

在Java运行时环境中运行的每个程序都分配有一个垃圾回收堆。因为类对象的实例是从此堆分配的,所以堆的另一个词是内存分配池。默认情况下,在大多数系统上,堆大小设置为1MB。

尽管在启动程序时将堆设置为特定大小,但是例如在分配新对象时,堆可能会增长。为了确保堆不会变得太大,Java虚拟机会自动释放或垃圾回收不再使用的对象。

Java执行自动垃圾收集作为后台线程。在Java运行时环境中运行的每个线程都有两个与之关联的堆栈:第一个堆栈用于Java代码;第二个堆栈用于Java代码。第二个用于C代码。这些堆栈使用的内存是从总系统内存池中提取的。每当新线程开始执行时,就会为Java代码和C代码分配最大的堆栈大小。默认情况下,在大多数系统上,Java代码堆栈的最大大小为400KB,而C代码堆栈的最大大小为128KB。

如果我们的系统有内存限制,我们可以强制Java执行更积极的清理,从而减少使用的内存总量。为此,请减小Java和C代码堆栈的最大大小。如果我们的系统有很多内存,我们可以强制Java执行不太积极的清理,从而减少后台处理量。为此,请增加Java和C代码堆栈的最大大小。

恒量池

堆中的每个类都有一个与之关联的常量池。因为常量不变,所以它们通常在编译时创建。常量池中的项目编码特定类中任何方法使用的所有名称。该类包含一个对存在多少个常量的计数,以及一个偏移量,该偏移量指定在类说明中特定的常量列表从何处开始。

与常量关联的所有信息均基于常量的类型遵循特定格式。例如,类级常量用于表示类或接口,并具有以下格式:

tag的值在哪里CONSTANT_Class,并且name_index提供string了类的名称。类名int[][]是[[I。类名Thread[]是[Ljava.lang.Thread;。

方法领域

Java的方法区域类似于其他编程语言使用的运行时环境的已编译代码区域。它存储与已编译代码中的方法相关联的字节码指令,以及执行环境进行动态链接所需的符号表。与该方法相关联的任何调试信息或其他信息也都存储在此区域中。

字节码指令集

尽管程序员更喜欢以高级格式编写代码,但是我们的计算机无法直接执行此代码,这就是为什么我们必须先编译Java程序,然后才能运行它们。通常,编译后的代码可以是称为机器语言的机器可读格式,也可以是诸如汇编语言或Java字节码的中间级格式。

Java虚拟机使用的字节码指令类似于汇编程序指令。如果您曾经使用过Assembler,则知道为了效率起见,将指令集精简到了最少,并且使用一系列指令来完成诸如打印到屏幕之类的任务。例如,Java语言允许我们使用单行代码在屏幕上打印,例如:

在编译时,Java编译器将单行打印语句转换为以下字节代码:

JDK提供了一种用于检查字节码的工具,称为Java类文件反汇编程序。我们可以通过在命令行中键入javap来运行反汇编程序。

由于字节码指令的格式很低,因此我们的程序几乎以编译为机器语言的程序的速度执行。机器语言中的所有指令均由0和1的字节流表示。在低级语言中,0和1的字节流被适当的助记符(例如字节代码指令)代替isub。与汇编语言一样,字节码指令的基本格式为:

因此,字节码指令集中的一条指令由一个1字节的操作码(指定要执行的操作)以及零个或多个提供该操作将使用的参数或数据的操作数组成。

摘要

Java虚拟机仅存在于我们计算机的内存中。在我们计算机内存中复制一台机器需要七个关键对象:一组寄存器,一个堆栈,一个执行环境,一个垃圾收集堆,一个常量池,一个方法存储区以及一种将它们捆绑在一起的机制。该机制是字节码指令集。

要检查字节码,我们可以使用Java类文件反汇编程序javap。通过详细检查字节码指令,我们获得了有关Java虚拟机和Java本身内部工作的宝贵见解。每个字节代码指令执行范围非常有限的特定功能,例如将对象压入堆栈或将对象弹出堆栈。这些基本功能的组合代表了复杂的高级任务,这些任务定义为Java编程语言中的语句。看起来如此惊人,有时会使用数十个字节代码指令来执行单个Java语句指定的操作。当我们将这些字节码指令与虚拟机的七个关键对象一起使用时,Java获得了平台独立性,成为世界上功能最强大,用途最广泛的编程语言。

问题:Java类反编译,使用什么命令?

答:javap命令

转发+关注后台私信回复我【资料】即可获得资料免费领取方式!

本文来自投稿,不代表本人立场,如若转载,请注明出处:http://www.souzhinan.com/kj/243192.html