深入Java虚拟机

JVM指令集与字节码的关系 | 字节码指令集的设计

Posted by Booogu on February 7, 2021
1528 字 5 分钟

“Booogu的第一篇技术博文”

2021年初的某个夜晚,Booogu决定开始记录一些想法和心得,就从手边这本书开始吧!

字节码介绍

  • JVM虚拟机的指令由一个字节长度的、代表着某种特定操作含义的数字(称为操作码,Opcode)以及跟随其后的零至多个代表此操作所需的参数(称为操作数,Oprand)构成。
  • Java采用的是基于栈而非寄存器的架构,大多数指令都不包含操作数,只有一个操作码,指令参数都存放在操作数栈中。
  • 限制Java虚拟机操作码的长度为一个字节(0-255),所以指令集总数不能超过256条。

JVM指令集与字节码的关系

  • JVM虚拟机的指令由一个字节长度的、代表着某种特定操作含义的数字(称为操作码,Opcode)以及跟随其后的零至多个代表此操作所需的参数(称为操作数,Oprand)构成
  • Java采用的是基于栈而非寄存器的架构,大多数指令都不包含操作数,只有一个操作码,指令参数都存放在操作数栈中。限制Java虚拟机操作码的长度为一个字节(0-255),所以指令集总数不能超过256条

此设计优劣势对比

JVM字节码指令集设计初衷是:追求尽可能小数据量、高传输效率的设计。这也是由Java语言设计之初主要面向网络、智能家电的技术背景决定的。 当JVM在处理超过1个字节长度的数据时,必须在运行时从字节中重建出具体数据的结构,譬如将一个16长度的无符号整数使用两个无符号字节存储起来,它们的值是: (byte1 « 8) | byte2。

  • 此设计的优势 放弃操作数长度对齐(一般都是单字节对齐),省略大量填充和间隔符号,用一个字节代表操作码,获得短小精悍的编译代码

  • 此设计的劣势 因为需要运行时重建数据结构(做运算),导致解释执行字节码时将损失部分性能

结合案例理解字节码设计 JVM在处理超过1个字节长度的数据时,必须在运行时从字节中重建出具体数据的结构,譬如将一个16长度的无符号整数使用两个无符号字节存储起来,它们的值是: (byte1 « 8) | byte2

设计存在的问题及解法

  • 前提 JVM字节码中,对于大部分与数据类型相关的字节码指令,操作码助记符中都有特殊的字符来表明专门为哪种数据类型服务:i代表int、l代表long等;也有一些指令的助记符中没有明确指明操作类型的字母,比如arraylength指令,它没有代表数据类型的特殊字符,但操作数永远只能是一个数组类型的对象、goto指令,与数据类型无关。

  • 问题 JVM长度只有一个字节,所以包含数据类型的操作码与指令组合,就为指令集的设计带来很大的压力;如果每一种与数据类型相关的指令都支持Java虚拟机所有运行时数据类型,那么会超出256数量限制。

  • 解法 JVM指令集对于特定的操作,只提供了有限的类型相关指令去支持它,换句话说,指令集被故意设计为“非完全独立”的。JVM规范称之为“NotOrthogonal”,即并非每种数据类型和每一种操作都有对应的指令,有一些单独的指令可以在必要时用来将一些不支持的类型转换为可被支持的类型。

  • 以JVM对各种数据类型相关的指令支持为例说明 byte、char、short都没有被大部分指令支持,boolean没有任何指令支持,编译器会在编译器/运行期将byte和short类型的数据带符号扩展为相应的int类型,将boolean和char零位扩展为相应的int。与之相似,在处理boolean、byte、short和char类型的数据时,也会转换为使用对应的int类型的字节码指令来处理。

常用字节码指令

参见:https://blog.csdn.net/itcats_cn/article/details/81113647

—— Booogu 于 2021.2.06 深夜整理自《深入理解Java虚拟机——第三版》