java虚拟_Java虚拟机(JVM)工作原理

虽然本教程的内容为 x86 处理器的原生汇编语言,但是了解其他机器架构如何工作也是有益的。JVM 是基于堆栈机器的首选示例。JVM 用堆栈实现数据传送、算术运算、比较和分支操作,而不是用寄存器来保存操作数(如同 x86 一样)。

数据结构,让它们协同工作。Java 字节码是指编译好的 Java 程序中使用的机器语言的名字。

JVM 执行的编译程序包含了 Java 字节码。每个 Java 源程序都必须编译为 Java 字节码(形式为 .class 文件)后才能执行。包含 Java 字节码的程序可以在任何安装了 Java 运行时软件的计算机系统上执行。

例如,一个 Java 源文件名为 Account.java,编译为文件 Account.class。这个类文件是该类中每个方法的字节码流。JVM 可能选择实时编译(just-in-time compilation)技术把类字节码编译为计算机的本机机器语言。

正在执行的 Java 方法有自己的堆栈帧存放局部变量、操作数栈、输入参数、返回地址和返回值。操作数区实际位于堆栈顶端,因此,压入这个区域的数值可以作为算术和逻辑运算的操作数,以及传递给类方法的参数。

在局部变量被算术运算指令或比较指令使用之前,它们必须被压入堆栈帧的操作数区域。通常把这个区域称为操作数栈(operand stack)。

Java 字节码中,每条指令包含 1 字节的操作码、零个或多个操作数。操作码可以用 Java 反汇编工具显示名字,如 iload、istore、imul 和 goto。每个堆栈项为 4 字节(32 位)。

查看反汇编字节码

Java 开发工具包(JDK)中的工具 javap.exe 可以显示 java.class 文件的字节码,这个操作被称为文件的反汇编。命令行语法如下所示:

javap -c classname

比如,若类文件名为 Account.class,则相应的 javap 命令行为:

javap -c Account

安装 Java 开发工具包后,可以在 \bin 文件夹下找到 javap.exe 工具。

指令集

1) 基本数据类型

JVM 可以识别 7 种基本数据类型,如下表所示。和 x86 整数一样,所有有符号整数都是二进制补码形式。但它们是按照大端顺序存放的,即高位字节位于每个整数的起始地址(x86 的整数按小端顺序存放)。

数据类型所占字节格式数据类型所占字节格式

char

2

Unicode 字符

long

8

有符号整数

byte

1

有符号整数

float

4

IEEE 单精度实数

short

2

有符号整数

double

8

IEEE 双精度实数

int

4

有符号整数

2) 比较指令

比较指令从操作数栈的顶端弹出两个操作数,对它们进行比较,再把比较结果压入堆栈。现在假设操作数入栈顺序如下所示:

34fa986749c2a53a7024bd3cd552e571.gif

下表给出了比较 op1 和 op2 之后压入堆栈的数值:

op1 和 op2 比较的结果压入操作数栈的数值

op1 > op2

1

op1 = op2

0

op1 < op2

-1

dcmp 指令比较双字,fcmp 指令比较浮点数。

3) 分支指令

分支指令可以分为有条件分支和无条件分支。Java 字节码中无条件分支的例子是 goto 和 jsr。

goto 指令无条件分支到一个标号:

goto label

jsr 指令调用用标号定义的子程序。其语法如下:

jsr label

条件分支指令通常检测从操作数栈顶弹出的数值。根据该值,指令决定是否分支到给定标号。比如,ifle 指令就是当弹出数值小于等于 0 时跳转到标号。其语法如下:

ifle label

同样,ifgt 指令就是当弹出数值大于等于 0 时跳转到标号。其语法如下:

ifgt label

Java 反汇编示例

为了帮助理解 Java 字节码是如何工作的,本节将给出用 Java 编写的一些短代码例子。在这些例子中,请注意不同版本 Java 的字节码清单细节会存在些许差异。

【示例 1】两个整数相加

下面的 Java 源代码行实现两个整数相加,并将和数保存在第三个变量中:

int A = 3;

int B = 2;

int sum = 0;

sum = A + B;

该 Java 代码的反汇编如下:

iconst_3

istore_0

iconst_2

istore_l

iconst_0

istore_2

iload_0

iload_l

iadd

istore_2

每个编号行表示一条 Java 字节码指令的字节偏移量。本例中,可以发现每条指令都只占一个字节,因为指令偏移量的编号是连续的。

尽管字节码反汇编一般不包括注释,这里还是会将注释添加上去。虽然局部变量在运行时堆栈中有专门的保留区域,但是指令在执行算术运算和数据传送时还会使用另一个堆栈,即操作数栈。为了避免在这两个堆栈间产生混淆,将用索引值来指代变量位置,如 0、1、2 等。

现在来仔细分析刚才的字节码。开始的两条指令将一个常数值压入操作数栈,并把同一个值弹出到位置为 0 的局部变量:

iconst_3 //常数(3)压入操作数栈

istore_0 //弹出到局部变量0

接下来的四行将其他两个常数压入操作数栈,并把它们弹岀到位置分别为 1 和 2 的局部变量:

iconst_2 //常数(2)压入操作数栈

istore_1 //弹出到局部变量1

iconst_0 //常数(0)压入操作数栈

istore_2 //弹出到局部变量2

由于已经知道了该生成字节码的 Java 源代码,因此,很明显下表列出的是三个变量的位置索引:

位置索引变量名

0

A

1

B

2

sum

接下来,为了实现加法,必须将两个操作数压入操作数栈。指令 iload_0 将变量 A 入栈,指令 iload_1 对变量 B 进行相同的操作:

iload_0 // (A 入栈)

iload_1 // (B 入栈)

现在操作数栈包含两个数:

645d22f6be74eca57c64c50c7a1e7f9b.gif

这里并不关心这些例子的实际机器表示,因此上图中的运行时堆栈是向上生长的。每个堆栈示意图中的最大值即为栈顶。

指令 iadd 将栈顶的两个数相加,并把和数压入堆栈:

iadd

操作数栈现在包含的是 A、B 的和数:

c3b451ba09a7f02277e05ad34c7ff2e2.gif

指令 istore_2 将栈顶内容弹出到位置为 2 的变量,其变量名为 sum:

istore_2

操作数栈现在为空。

【示例 2】两个 Double 类型数据相加

下面的 Java 代码片段实现两个 double 类型的变量相加,并将和数保存到 sum。它执行的操作与两个整数相加示例相同,因此这里主要关注的是整数处理与 double 处理的差异:

double A = 3.1;

double B = 2;

double sum = A + B;

本例的反汇编字节码如下所示,用 javap 实用程序可以在右边插入注释:

ldc2_w #20; // double 3.Id

dstore_0

ldc2_w #22; // double 2.Od

dstore_2

dload_0

dload_2

dadd

dstore_4

下面对这个代码进行分步讨论。偏移量为 0 的指令 ldc2_w 把一个浮点常数(3.1)从常数池压入操作数栈。ldc2 指令总是用两个字节作为常数池区域的索引:

ldc2_w #20; // double 3.ld

偏移量为 3 的 dstore 指令从堆栈弹出一个 double 数,送入位置为 0 的局部变量。该指令起始偏移量(3)反映出第一条指令占用的字节数(操作码加上两字节索引):

dstore_0 //保存到 A

同样,接下来偏移量为 4 和 7 的两条指令对变量 B 进行初始化:

ldc2_w #22; // double 2.Od

dstore_2 // 保存到 B

指令 dload_0 和 dload_2 把局部变量入栈,其索引指的是 64 位位置(两个变量栈项),因为双字数值要占用 8 个字节:

dload_0

dload_2

接下来的指令(dadd)将栈顶的两个 double 值相加,并把和数入栈:

dadd

最后,指令 dstore_4 把栈顶内容弹出到位置为 4 的局部变量:

dstore_4

JVM 条件分支

了解 JVM 怎样处理条件分支是理解 Java 字节码的重要一环。比较操作总是从堆栈栈顶弹出两个数据,对它们进行比较后,再把结果数值入栈。条件分支指令常常跟在比较操作的后面,利用栈顶数值决定是否分支到目标标号。比如,下面的 Java 代码包含一个简单的 IF 语句,它将两个数值中的一个分配给一个布尔变量:

double A = 3.0;

boolean result = false;

if( A > 2.0 )

result = false;

else

result = true;

该 Java 代码对应的反汇编如下所示:

ldc2_w #26; // double 3.Od

dstore_0 // 弹出到 A

iconst_0 // false = 0

istore_2 //保存到 result

dload_0

ldc2_w #22; // double 2.0d

dcmpl

ifle 19 //如果 A ≤ 2.0,转到 19

iconst_0 // false

istore_2 // result = false

goto 21 //跳过后面两条语句

iconst_l // true

istore_2 // result = true

开始的两条指令将 3.0 从常数池复制到运行时堆栈,再把它从堆栈弹岀到变量 A:

ldc2_w #26; // double 3.0d

dstore_0 // 弹出至A

接下来的两条指令将布尔值 false (等于 0)从常量区复制到堆栈,再把它弹出到变量 result:

iconst_0 // false = 0

istore_2 // 保存到 result

A 的值(位置 0)压入操作数栈,数值 2.0 紧跟其后入栈:

dload_0     //A 入栈

ldc2_w #22; // double 2.0d

操作数栈现在有两个数值:

5b957ea6894b4f39764038e4dade1904.gif

指令 dcmpl 将两个 double 数弹出堆栈进行比较。由于栈顶的数值(2.0)小于它下面的数值(3.0),因此整数 1 被压入堆栈。

dcmpl

如果从堆栈弹出的数值小于等于 0,则指令 ifle 就分支到给定的偏移量:

ifle 19   //如果 stack.pop() <= 0,转到 19

这里要回顾一下之前给出的 Java 源代码示例,若 A>2.0,其分配的值为 false:

if( A > 2.0 )

result = false;

else

result = true;

如果 A <= 2.0,Java 字节码就把 IF 语句转向偏移量为 19 的语句,为 result 分配数值 true。与此同时,如果不发生到偏移量 19 的分支,则由下面几条指令把 false 赋给 result:

iconst_0     // false

istore_2     // result = false

goto 21     //跳过后面两条指令

偏移量 16 的指令 goto 跳过后面两行代码,它们的作用是给 result 分配 true:

iconst_l // true

istore_2 // result = true

Java 虚拟机的指令集与 x86 处理器系列的指令集有很大的不同。它采用面向堆栈的方法实现计算、比较和分支,与 x86 指令经常使用寄存器和内存操作数形成了鲜明的对比。

虽然字节码的符号反汇编不如 x86 汇编语言简单,但是,编译器生成字节码也是相当容易的。每个操作都是原子的,这就意味着它只执行一个操作。

若 JVM 使用的是实时编译器,则 Java 字节码只要在执行前转换为本地机器语言即可。就这方面来说,Java 字节码与基于精简指令集(RISC)模型的机器语言有很多共同点。

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

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

相关文章

MVC5 + EF6 + Bootstrap3

MVC5 EF6 Bootstrap3 (16) 客户端验证摘要: 本节介绍MVC客户端验证。阅读全文posted 2015-05-26 07:15 Slark.NET 阅读(6333) | 评论 (21) 编辑MVC5 EF6 Bootstrap3 (15) 应用ModelState和Data Annotation做服务器端数据验证摘要: 本节我们用两种不同的方法ModelState和Da…

java for循环break_Java中break、continue、return在for循环中的使用

这篇文章主要介绍了break、continue、return在for循环中的使用&#xff0c;本文是小编收藏整理的&#xff0c;非常具有参考借鉴价值,需要的朋友可以参考下引言&#xff1a;在使用循环的时候&#xff0c;循环里面带有break、continue、return的时候经常弄混&#xff0c;今天特意…

python 定义字符串变量_python 字符串(1)

字符串 操练一下字符串吧。 >>> print "good good study, day day up" good good study, day day up 在python中&#xff0c;通常用一对双引号、或者单引号来包裹一个字符串。或者说&#xff0c;要定义一个字符串&#xff0c;就用双引号或者单引号。 转义字…

play 拦截器_编写Play 2的模块,第2部分:拦截器

play 拦截器在本教程的第一部分中&#xff0c;我们介绍了创建&#xff0c;发布和调用模块的基本知识。 我们创建的模块并没有真正做很多事情&#xff0c;因此现在是时候使用Play的某些功能来扩展功能了。 1.拦截器 拦截器使您可以拦截对控制器的调用&#xff0c;并增强或阻止其…

java string blob_java String类型转换为Blob类型

展开全部这个是mysql下存取blob字段的一个很简单的类&#xff0c;跟据自己的需要32313133353236313431303231363533e4b893e5b19e31333332623936改改就行了/*** Title: BlobPros.java* Project: test* Description: 把图片存入mysql中的blob字段&#xff0c;并取出* Call Module…

架构之Nginx(负载均衡/反向代理)

Nginx (“engine x”) 是一个高性能的 HTTP 和 反向代理 服务器 &#xff0c;也是一个 IMAP/POP3/SMTP 代理 服务器 。 Nginx 是由 Igor Sysoev 为俄罗斯访问量第二的 Rambler.ru 站点开发的&#xff0c;第一个公开版本0.1.0发布于2004年10月4日。其将源代码以类BSD许可证的形式…

Neo4j:特定关系与一般关系+属性

为了在Neo4j查询中获得最佳的遍历速度&#xff0c;我们应该使关系类型尽可能具体 。 让我们看一下几周前我在Skillsmatter上发表的“ 建模建议引擎建模 ”演讲中的一个例子。 我需要决定如何为成员和事件之间的“ RSVP”关系建模。 一个人可以对事件表示“是”或“否”&#…

java 获取 网卡名称_Java获取网卡信息详解

InterfaceAddress 类表示一个由名称和分配给此接口的 IP 地址列表组成的网络接口。它用于标识加入多播组的本地接口。 接口通常是按名称(如 "le0")区分的。NetworkParameterDemo.javaimport java.net.InterfaceAddress;import java.net.NetworkInterface;import java…

java 按位置格式化字符串_Java字符串格式化,{}占位符根据名字替换实例

我就废话不多说了&#xff0c;大家还是直接看代码吧~import java.beans.PropertyDescriptor;import java.lang.reflect.Method;import java.util.HashMap;import java.util.Map;import java.util.regex.Matcher;import java.util.regex.Pattern;public class StringFormatUtil …

Dropzone.js实现文件拖拽上传

dropzone.js是一个开源的JavaScript库&#xff0c;提供 AJAX 异步文件上传功能&#xff0c;支持拖拽文件、支持最大文件大小、支持设置文件类型、支持预览上传结果&#xff0c;不依赖jQuery库。 查看演示 下载源码使用Dropzone 我们可以建立一个正式的上传form表单&#xff0c;…

java 获取系统时间 8小时 jre_Java获取时间与系统时间相差8小时终极解决方案

0、引言Druid中时区的问题一直困扰着我们&#xff0c;所以我专门去研究了一下世界时区和Java中的时区&#xff0c;对使用Druid很用帮助.1、UTC时间&GMT时间UTC时间是时间标准时间(Universal Time Coordinated)&#xff0c;UTC是根据原子钟来计算时间&#xff0c;误差非常小…

Apache Drill 1.4性能增强的简要概述

今天&#xff0c;我们很高兴宣布Apache Drill 1.4现已在MapR发行版中可用。 钻1.4是MAPR生产就绪和支持的版本&#xff0c;可以从下载这里 &#xff0c;找到1.4版本说明这里 。 Drill 1.4以其高度灵活和可扩展的体系结构为基础&#xff0c;带来了多种新功能以及对查询性能的增…

【01背包】洛谷P1282多米诺骨牌

题目描述 多米诺骨牌有上下2个方块组成&#xff0c;每个方块中有1~6个点。现有排成行的 上方块中点数之和记为S1&#xff0c;下方块中点数之和记为S2&#xff0c;它们的差为|S1-S2|。例如在图8-1中&#xff0c;S161119&#xff0c;S2153211&#xff0c;|S1-S2|2。每个多米诺骨牌…

java geolitecity_GeoLite2 Java根据IP获得城市、经纬度

之前我们介绍过通过 qqwry.dat 根据IP获得所属城市和运营商信息。但是这个 qqwry.dat 已经太久没更新了&#xff0c;数据有些不准确&#xff0c;而且现在我们有个需求就是想获取某个IP所在的经纬度。这里我们可以使用 GeoLite2&#xff0c;这个是国外开源的一个库&#xff0c;需…

Leetcode:search_insert_position

一、 题目 给定一个数组和要插入数的大小。求插入的位置。 二、 分析 太水&#xff0c;直接扫描。过……. class Solution { public:int searchInsert(int A[], int n, int target) {for(int i0;i<n;i) {if(target<A[i]) {return i;} }return n;} };转载于:https…

java mvc 面试题_2018年java技术面试题整理

1、servlet执行流程客户端发出http请求&#xff0c;web服务器将请求转发到servlet容器&#xff0c;servlet容器解析url并根据web.xml找到相对应的servlet&#xff0c;并将request、response对象传递给找到的servlet&#xff0c;servlet根据request就可以知道是谁发出的请求&…

交叉编译指定运行时库路径_运行时vs编译时类路径

交叉编译指定运行时库路径这确实应该是一个简单的区别&#xff0c;但是我一直在回答有关Stackoverflow的许多类似问题&#xff0c;并且经常有人误解此事。 那么&#xff0c;什么是类路径&#xff1f; 应用程序所需的一组所有类&#xff08;以及带有类的jar&#xff09;的集合。…

计算机专业英语第二版张强华翻译_计算机语言发展的三个阶段,机器语言、汇编语言与高级语言...

在如今信息发达的时代&#xff0c;科技日新月异&#xff0c;计算机和Internet网络的发展也成为人们日常生活的重要部分。学习一两门计算机编程语言也如当初学习英文一样的火热&#xff0c;随着人工智能AI和云计算的不断发展&#xff0c;Python语言和Scala语言已经成为这两个领域…

java netty html_源码时代JAVA干货分享|带你用Netty框架实现WebSocket通信

功能介绍Netty开发服务器HTML实现客户端实现服务端与客户端时实时交互开发步骤1.导包io.nettynetty‐all5.0.0.Alpha22.工程配置文件&#xff1a;NettyConfig/*** 这里放的是工程中相应的配置*/public class NettyConfig{/*** 用于存储每一个客户端接入进来时的channel对象*/pu…

使用Apache Drill REST API通过Node构建ASCII仪表板

Apache Drill有一个隐藏的瑰宝&#xff1a;易于使用的REST接口。 该API可用于查询&#xff0c;分析和配置Drill引擎。 在此博客文章中&#xff0c;我将说明如何使用Brilled Contrib使用Drill REST API创建ascii仪表板。 ASCII仪表盘如下所示&#xff1a; 先决条件 Node.js …