按照《java虚拟机规范SE7》章节顺序整理的笔记。

第二章:java虚拟机的结构


目录:

  1. 数据类型
  2. 运行时数据区
  3. 栈帧
  4. 浮点算法
  5. 字节码指令集

一. 数据类型

虚拟机可以操作的类型可以分为两类:

  1. 原始类型
  2. 引用类型

<1>. 原始类型

又称原生类型,基本类型

原始类型包含 数值类型(byte,short,int,long,float,double,char)布尔类型(boolean)returnAddress类型 三类,它们除了returnAddress类型,其它都与java语言中的类型对应。

1. 数值类型

对于jvm中的数值类型,最难理解的在于规范中对浮点类型的描述。returnAddress是唯一一个不能与java语言对应起来的类型。

数值类型又分为

  1. 整型类型
  2. 浮点类型
a. 整型类型:byte, short, int, long, char
类型 大小 存储方式 取值范围 默认值
byte 1个字节(8位) 有符号二进制补码整数 -2^7 到 2^7-1 0
short 2个字节(16位) 有符号二进制补码整数 -2^15 到 2^15-1 0
int 4个字节(32位) 有符号二进制补码整数 -2^31 到 2^31-1 0
long 8个字节(64位) 有符号二进制补码整数 -2^63 到 2^63-1 0
char 2个字节(16位) 无符号二进制正整数 0 到 2^8-1 unicode编码的null
b. 浮点类型:float, double

浮点类型一共有7种类型的值:负无穷,可数负数、正负零、可数正数、正无穷、NaN。

其中NaN:用于表示某些无效的运算操作,例如除数为零等情况。

浮点数的取值集合一共有四种:

  • 单精度浮点数集合(标准)
  • 单精度扩展指数集合
  • 双精度浮点数集合(标准)
  • 双精度扩展指数集合

必须实现标准,选择实现非标准: 所有Java虚拟机的实现都必须支持两种标准的浮点数值集合:单精度浮点数集合和双精度浮点数集合。另外,Java虚拟机实现可以自由选择是否要支持单精度扩展指数集合和双精度扩展指数集合,也可以选择支持其中的一种或全部。

集合间的包容性: 经过精心设计,保证了每一个单精度浮点数集合中的元素都一定是单精度扩展指数集合、双精度浮点数集合和双精度扩展指数集合中的元素。与此类似,每一个双精度浮点数集合中的元素,都一定是双精度扩展指数集合的元素。换句话说,每一种扩展指数集合都有比相应的标准浮点数集合更大的指数取值范围,但是不会有更高的精度。

正负零的区别: 浮点数中,正数零和负数零是相等的,但是它们有一些操作会有区别。例如1.0除以0.0会产生正无穷大的结果,而1.0除以-0.0则会产生负无穷大的结果。

NaN的特殊之处: NaN是无序的,对它进行任何的数值比较和等值测试都会返回false的比较结果。值得一提的是,有且只有NaN一个数与自身比较是否数值上相等时会得到false的比较结果,任何数字与NaN进行非等值比较都会返回true。

2. 布尔类型

在java虚拟机中,大多数时候,对布尔类型进行运算时都会转换成int类型,这是因为在java虚拟机的字节码指令集中,没有直接对布尔类型的操作指令。

但是指令集中有直接对布尔数组的操作,如newarray便可以创建一个原始类型的数组。

boolean的数组类型的访问与修改共用byte类型数组的baload和bastore指令。

3. returnAddress类型

returnAddress类型的值指向一条虚拟机指令的操作码,即字节码指令。

与前面介绍的那些数值类的原始类型不同,returnAddress类型在Java语言之中并不存在相应的类型,也无法在程序运行期间更改returnAddress类型的值。

<2>. 引用类型 (reference)

Java虚拟机中一种有三种引用类型:

  1. 类类型
  2. 数组类型
  3. 接口类型

其中接口类型就是实现了此接口的类或者数组,所以实质上就是前两种类型。

数组类型的组件类型: 数组类型包含了一个组件类型,这个组件类型也可以是一个数组,如果组件类型还是数组,那么必须保证,这样一直下去,最终组件类型一定是非数组,这种类型叫数组的元素类型。元素类型可以是:原始类型,类类型,接口类型。

没有引用对象时使用null: 一个为null的引用,在没有上下文的情况下不具备任何实际的类型,但是有具体上下文时它可转型为任意的引用类型。引用类型的默认值就是null。


二. 运行时数据区

运行时数据区有:

  • pc寄存器
  • java虚拟机栈
  • 本地方法栈
  • 方法区
  • java堆
  • 运行时常量池

根据不同区域的创建销毁时间以及使用的限制可分为:线程级,共享级。

线程级有:pc寄存器,java虚拟机栈,本地方法栈。

共享级有:方法区,java堆,运行时常量池。

其中线程级表示这些数据区的生命周期与线程息息相关,而共享级表示这些数据区能被所有线程共享。

<1>. pc寄存器

  • 每个java虚拟机线程都有一个pc寄存器,任意时刻,线程只能运行一个方法,叫当前方法(参考后面的 栈帧 一节)。

作用:存储至少一个returnAddress类型的数据,returnAddress存储一条字节码指令。

作用范围: 线程内。

创建: 随着线程的创建而分配。

使用:如果pc寄存器所在的线程调用的是native本地方法,那么pc寄存器的值为undefined;如果调用不是本地方法,那么值为正在执行的字节码指令。用于后面栈帧中对操作数栈的使用。

<2>. java虚拟机栈

  • 每个java虚拟机线程都有一个java虚拟机栈。

作用: 用于存储栈帧(栈帧:每个方法的执行都会创建一个栈帧,因此,一个栈帧就代表一个方法的调用)。

作用范围: 线程内。

创建和销毁: 与线程同步。

大小:

  • 固定大小
  • 根据计算动态扩展和收缩

内存分配: 因为除了栈帧的出栈和入栈之外,Java虚拟机栈不会再受其他因素的影响,所以栈帧可以在堆中分配,Java虚拟机栈所使用的内存不需要保证是连续的。

可能抛出的异常:

  • 如果线程请求分配的栈容量超过Java虚拟机栈允许的最大容量时,Java虚拟机将会抛出一个StackOverflowError异常。
  • 如果Java虚拟机栈可以动态扩展,并且扩展的动作已经尝试过,但是目前无法申请到足够的内存去完成扩展,或者在建立新的线程时没有足够的内存去创建对应的虚拟机栈,那Java虚拟机将会抛出一个OutOfMemoryError异常。

<3>. 本地方法栈

  • 本地方法栈与native密切相关,是java虚拟机在调用外部方法时使用的区域。
  • 本地方法站对于Java虚拟机并不是必须的。

作用: 支持native方法的执行。

创建和销毁: 与线程同步。

大小:

  • 固定大小
  • 根据计算动态扩展和收缩

可能抛出的异常:

  • 如果线程请求分配的栈容量超过本地方法栈允许的最大容量时,Java虚拟机将会抛出一个StackOverflowError异常。
  • 如果本地方法栈可以动态扩展,并且扩展的动作已经尝试过,但是目前无法申请到足够的内存去完成扩展,或者在建立新的线程时没有足够的内存去创建对应的本地方法栈,那Java虚拟机将会抛出一个OutOfMemoryError异常。

<4>. 方法区

  • 方法区(Method Area)是可供各条线程共享的运行时内存区域
  • 方法区是堆的逻辑组成部分
  • 方法区在实际内存空间中可以是不连续的。

作用: 存储类的结构信息。它包括:

  • 运行时常量池(Runtime Constant Pool)、
  • 字段
  • 方法数据
  • 构造函数
  • 普通方法的字节码内容
  • 还包括一些在类、实例、接口初始化时用到的特殊方法。

创建与销毁: 随着虚拟机的启动而创建。

作用范围: 所有线程共享。

大小:

  • 固定大小
  • 根据计算动态扩展和收缩

可能抛出的异常:

  • 如果方法区的内存空间不能满足内存分配请求,那Java虚拟机将抛出一个OutOfMemoryError异常。

<5>. java堆

  • Java堆所使用的内存不需要保证是连续的。

作用: 存储所有new的对象,包括实例对象,数组对象等。

作用范围: 所有线程共享。

创建与销毁: 随着虚拟机的启动而创建。

大小:

  • 固定大小
  • 根据计算动态扩展和收缩

可能抛出的异常:

  • 如果实际所需的堆超过了自动内存管理系统能提供的最大容量,那Java虚拟机将会抛出一个OutOfMemoryError异常。

<6>. 运行时常量池

  • 运行时常量池分配在方法区中,是方法区的一部分。

作用: 存储数值字面量,以及必须运行期解析后才能获得的方法或字段的引用。

作用范围: 所有线程共享。

创建与销毁: 在类和接口被加载到虚拟机后,对应的运行时常量池就被创建出来。

大小:

  • 固定大小
  • 根据计算动态扩展和收缩

可能抛出的异常:

  • 当创建类或接口的时候,如果构造运行时常量池所需要的内存空间超过了方法区所能提供的最大值,那Java虚拟机将会抛出一个OutOfMemoryError异常。

三. 栈帧

作用: 栈帧(Frame)是用来存储数据和部分过程结果的数据结构,同时也被用来处理动态链接(Dynamic Linking)、方法返回值和异常分派(Dispatch Exception)。

创建与销毁: 栈帧随着方法调用而创建,随着方法结束而销毁。

存储位置: Java虚拟机栈。

栈帧的结构:

  • 局部变量表
  • 操作数栈
  • 指向当前方法所属类的运行时常量池引用

栈帧的大小: 仅取决于Java虚拟机的实现和方法调用时可被分配的内存。

当前栈帧,当前方法,当前类: 在一条线程之中,只有目前正在执行的那个方法的栈帧是活动的。这个栈帧就被称为是当前栈帧(Current Frame),这个栈帧对应的方法就被称为是当前方法(Current Method),定义这个方法的类就称作当前类(Current Class)。对局部变量表和操作数栈的各种操作,通常都指的是对当前栈帧的对局部变量表和操作数栈进行的操作。

注: 栈帧是线程本地私有的数据,不可能在一个栈帧之中引用另外一条线程的栈帧。

<1>. 局部变量表

局部变量表用于存储基本类型的数据,这些数据将会用于字节码指令的操作数中。

局部变量表的长度在编译器决定。

局部变量使用索引来进行定位访问,第一个局部变量的索引值为零,局部变量的索引值是从零至小于局部变量表最大容量的所有整数。

Java虚拟机使用局部变量表来完成方法调用时的参数传递,当一个方法被调用的时候,它的参数将会传递至从0开始的连续的局部变量表位置上。特别地,当一个实例方法被调用的时候,第0个局部变量一定是用来存储被调用的实例方法所在的对象的引用(即Java语言中的“this”关键字)。后续的其他参数将会传递至从1开始的连续的局部变量表位置上。

<2>. 操作数栈

每个栈帧都包含一个操作数栈,它的大小在编译期决定。

操作数栈用于运算存储操作数以及存储操作返回的结果。

<3>. 动态链接

动态链接的作用就是将符号引用所表示的方法转换为实际方法的直接引用。类加载的过程中将要解析掉尚未被解析的符号引用,并且将变量访问转化为访问这些变量的存储结构所在的运行时内存位置的正确偏移量。


四. 浮点算法

<1>. 浮点模式

每一个方法都有一项属性称为浮点模式(Floating-Point Mode),取值有两种,要么是FP-strict模式要么是非FP-strict模式

我们说一个操作数栈具有某种浮点模式时,所指的就是包含操作数栈的栈帧所对应的方法具备的浮点模式,类似的,我们说一条Java虚拟机字节码指令具备某种浮点模式,所指的也是包含这条指令的方法具备的浮点模式。


五. 字节码指令集

字节码指令的大小为一个字节,也就是说字节码指令集的大小不能超过一个字节大小可表示的数。

忽略异常处理,java虚拟机的解释器处理过程如下:

do { 自动计算PC寄存器以及从PC寄存器的位置取出操作码;if (存在操作数) 取出操作数;执行操作码所定义的操作
} while (处理下一次循环);
数据类型与Java虚拟机

虚拟机字节码指令大多数都包含了操作数的类型,如iadd。但是并不是每个与数值类型相关的指令都有每个类型的指令,否则一个字节就装不下了。

字节码指令中:i代表对int类型的数据操作,l代表long,s代表short,b代表byte,c代表char,f代表float,d代表double,a代表reference。

大部分的指令都没有支持整数类型byte、char和short,甚至没有任何指令支持boolean类型。在实际情况中,使用这些类型都会转换为int类型。

加载和存储指令

加载和存储指令用于将数据从栈帧的局部变量表和操作数栈之间来回传输:

  • 将一个局部变量加载到操作栈的指令包括有:iload、iload_、lload、lload_、fload、fload_、dload、dload_、aload、aload_
  • 将一个数值从操作数栈存储到局部变量表的指令包括有:istore、istore_、lstore、lstore_、fstore、fstore_、dstore、dstore_、astore、astore_
  • 将一个常量加载到操作数栈的指令包括有:bipush、sipush、ldc、ldc_w、ldc2_w、aconst_null、iconst_m1、iconst_、lconst_、fconst_、dconst_
  • 扩充局部变量表的访问索引的指令:wide
运算指令

算术指令用于对两个操作数栈上的值进行某种特定运算,并把结果重新存入到操作栈顶。

大体上运算指令可以分为两种:对整型数据进行运算的指令与对浮点型数据进行运算的指令,无论是那种算术指令,都是使用Java虚拟机的数字类型的。

Java虚拟机没有明确规定整型数据溢出的情况,但是规定了在处理整型数据时,只有除法指令(idiv和ldiv)以及求余指令(irem和lrem)出现除数为零时会导致虚拟机抛出异常,如果发生了这种情况,虚拟机将会抛出ArithmeitcException异常。

类型转换指令

1.小范围类型向大范围类型的安全转换

  • int类型到long、float或者double类型
  • long类型到float、double类型
  • float类型到double类型

这里没有包含,byte,short,char,boolean是因为这四个类型会自动转换为int,而int就可以继续转换了。

2.大范围类型向小范围类型的不安全转换

窄化类型转换可能会导致转换结果产生不同的正负号、不同的数量级,转换过程很可能会导致数值丢失精度。

在将int或long类型窄化转换为整数类型T的时候,转换过程仅仅是简单的丢弃除最低位N个字节以外的内容,N是类型T的数据类型长度,这将可能导致转换结果与输入值有不同的正负。

Java虚拟机中数值类型的窄化转换永远不可能导致虚拟机抛出运行时异常。

对象创建与操作

虽然类实例和数组都是对象,但Java虚拟机对类实例和数组的创建与操作使用了不同的字节码指令:

  • 创建类实例的指令:new
  • 创建数组的指令:newarray,anewarray,multianewarray
  • 访问类字段(static字段,或者称为类变量)和实例字段(非static字段,或者成为实例变量)的指令:getfield、putfield、getstatic、putstatic、faload、daload、aaload
  • 把一个数组元素加载到操作数栈的指令:baload、caload、saload、iaload、laload、faload、daload、aaload
  • 将一个操作数栈的值储存到数组元素中的指令:bastore、castore、sastore、iastore、fastore、dastore、aastore
  • 取数组长度的指令:arraylength
  • 检查类实例类型的指令:instanceof、checkcast
控制转移指令

控制转移指令可以让Java虚拟机有条件或无条件地从指定指令而不是控制转移指令的下一条指令继续执行程序。

  • 条件分支:ifeq、iflt、ifle、ifne、ifgt、ifge、ifnull、ifnonnull、if_icmpeq、if_icmpne、if_icmplt, if_icmpgt、if_icmple、if_icmpge、if_acmpeq和if_acmpne。
  • 复合条件分支:tableswitch、lookupswitch
  • 无条件分支:goto、goto_w、jsr、jsr_w、ret

由于各种类型的比较最终都会转化为int类型的比较操作,基于int类型比较的这种重要性,Java虚拟机提供了非常丰富的int类型的条件分支指令。

方法调用和返回指令
  • invokevirtual指令用于调用对象的实例方法,根据对象的实际类型进行分派(虚方法分派),这也是Java语言中最常见的方法分派方式。
  • invokeinterface指令用于调用接口方法,它会在运行时搜索一个实现了这个接口方法的对象,找出适合的方法进行调用。
  • invokespecial指令用于调用一些需要特殊处理的实例方法,包括实例初始化方法(§2.9)、私有方法和父类方法。
  • invokestatic指令用于调用类方法(static方法)。
  • return指令供声明为void的方法、实例初始化方法、类和接口的类初始化方法使用。
抛出异常

在程序中显式抛出异常的操作会由athrow指令实现,除了这种情况,还有别的异常会在其他Java虚拟机指令检测到异常状况时由虚拟机自动抛出。

同步

同步均使用了管程来支持的。

1.方法级同步

  • 方法级的同步是隐式
  • 虚拟机可以从方法常量池中的方法表结构中的ACC_SYNCHRONIZED访问标志区分一个方法是否同步方法。当方法调用时,调用指令将会检查方法的ACC_SYNCHRONIZED访问标志是否被设置,如果设置了,执行线程将先持有管程,然后再执行方法,最后再方法完成(无论是正常完成还是非正常完成)时释放管程。在方法执行期间,执行线程持有了管程,其他任何线程都无法再获得同一个管程。

2.方法内部一段指令序列的同步

  • 同步一段指令集序列通常是由Java语言中的synchronized块来表示的
  • Java虚拟机的指令集中有monitorenter和monitorexit两条指令来支持synchronized关键字的语义,正确实现synchronized关键字需要编译器与Java虚拟机两者协作支持。
虚拟机实现的方式主要有以下两种:
  • 将输入的Java虚拟机代码在加载时或执行时翻译成另外一种虚拟机的指令集
  • 将输入的Java虚拟机代码在加载时或执行时翻译成宿主机CPU的本地指令集(有时候被称Just-In-Time代码生成或JIT代码生成)

《java虚拟机规范SE7》整理——第二章:Java虚拟机结构相关推荐

  1. 【深入理解Java虚拟机学习笔记】第二章 Java 内存区域与内存溢出异常

    最近想好好复习一下java虚拟机,我想通过深读 [理解Java虚拟机 jvm 高级特性与最佳实践] (作者 周志明) 并且通过写一些博客总结来将该书读薄读透,这里文章内容仅仅是个人阅读后简短总结,加强 ...

  2. 【Java基础系列教程】第二章 Java语言概述、配置环境变量

    一.计算机编程语言概述 1.1 语言 语言(英文:Language)是人类进行沟通交流的表达方式.例如:中国人与中国人用普通话沟通.而中国人要和英国人交流,就要学习英语.              语 ...

  3. 《java虚拟机规范SE7》整理——第三章:为Java虚拟机编译

    按照<java虚拟机规范SE7>章节顺序整理的笔记. 目录: 常量.局部变量的使用和控制结构 算术运算 访问运行时常量池 接收参数 方法调用 使用类实例 数组 编译switch语句 抛出异 ...

  4. 《java虚拟机规范SE7》整理——第四章:Class文件格式

    按照<java虚拟机规范SE7>章节顺序整理的笔记. 目录: ClassFile格式(注:也就是class文件的总结构) 描述符和签名 常量池 字段 方法 属性 Java虚拟机代码约束 C ...

  5. 《java虚拟机规范SE7》整理——第五章:加载,链接与初始化

    按照<java虚拟机规范SE7>章节顺序整理的笔记. 目录: 运行时常量池 虚拟机启动 创建和加载 链接 初始化 绑定本地方法实现 Java虚拟机退出 第四章:加载,链接与初始化 java ...

  6. 深入Java虚拟机读书笔记第五章Java虚拟机

    Java虚拟机 Java虚拟机之所以被称之为是虚拟的,就是因为它仅仅是由一个规范来定义的抽象计算机.因此,要运行某个Java程序,首先需要一个符合该规范的具体实现. Java虚拟机的生命周期 一个运行 ...

  7. java 计数器越界,[总结]-第二章 Java内存区域与内存溢出异常

    [总结]-第二章 Java内存区域与内存溢出异常 一.知识点 1.虚拟机运行时数据区 方法区:运行时常量池(JDK1.7被移出) 堆:存放对象实例或数组.新生代和老年代 虚拟机栈:线程私有.栈 本地方 ...

  8. 描述java源程序构成_Java第二章Java程序设计

    <Java第二章Java程序设计>由会员分享,可在线阅读,更多相关<Java第二章Java程序设计(140页珍藏版)>请在人人文库网上搜索. 1.第2章 Java基本语法,2. ...

  9. 使用java实现面向对象编程第二章_java面向对象编程——第二章 java基础语法

    第二章 java基础语法 1.java关键字 abstract boolean break byte case catch char class const continue default do d ...

最新文章

  1. android button背景图片自适应,Android开发之给你的Button加个背景
  2. LINUX相关的镜像源网站大全,个人收集完整版!
  3. C#宿舍管理系统数据表文档分析含释义
  4. nodejs 环境搭建
  5. 排列组合算法之二: 01转换法_java改变后的c++改进版
  6. Excel 二次开发系列(3): 创建Excel二次开发环境
  7. plsql突然无法连接数据库,原来是tnsnames.ora文件出了问题
  8. nbu备份oracle rac,利用NETBACKUP将备份写到磁盘上
  9. 怎样在 SSASserver之间做同步
  10. String、StringBuffer 与StringBuilder
  11. 时间序列分类实践介绍(使用Python代码)
  12. 部署静态网站的五种方法
  13. java高德地图api开发平台_【高德地图API】从零开始学高德JS API(一)地图展现...
  14. 转载:domain adaption
  15. SLAM优秀开源工程大汇总
  16. 【kimol君的无聊小发明】—用python写截屏小工具
  17. 红黑树、b+树、b树、mysql索引详细剖析
  18. android 选择银行类型,vue2.0实现银行卡类型种类的选择
  19. web核心 8-filter过滤器 listener监听器 相对路径 绝对路径 项目名路径
  20. WeifenLuo.WinFormsUI.Docking的使用 z

热门文章

  1. 11gr2全外连接优化执行计划(二)
  2. 数学建模——种群竞争模型
  3. CSDN高校俱乐部招募啦,你还在等什么?
  4. BIOS信息中英文对照表
  5. camera相机亮度测算与曝光
  6. 编写高性能JavaScript
  7. 将eps文件控制在300K左右
  8. 女人不成熟的10大表现
  9. 连接SSH失败的原因以及方法
  10. Waves效果器离线安装包-Waves v9r30 Offline Install WiN-MAC