摘要: Jvm Structure
编译器应当在编译期间尽最大努力完成可能的类型检查,使得虚拟机在运行期间无需进行这些操作 编译器会在编译期或运行期会将byte和short类型的数据带符号扩展(Sign-Extend)为相应的int类型数据,将boolean和char类型数据零位扩展(Zero-Extend)为相应的int类型数据
程序运行期间会使用到的运行时数据区
每一个虚拟机线程都有自己的PC寄存器,保存Java虚拟机正在执行的字节码指令的地址
栈与线程同时创建,存储局部变量与一些过程结果的地方
可供各条线程共享的运行时内存区域,也是供所有类实例和数组对象分配内存的区域
Java堆在虚拟机启动的时候就被创建,它存储了被自动内存管理系统所管理的各种对象,这些受管理的对象无需,也无法显式地被销毁
可供各条线程共享的运行时内存区域
方法区在虚拟机启动的时候被创建,存储了每一个类的结构信息,例如运行时常量池、字段和方法数据、构造函数和普通方法的字节码内容、还包括一些在类、实例、接口初始化时用到的特殊方法
虽然方法区是堆的逻辑组成部分,但是简单的虚拟机实现可以选择在这个区域不实现垃圾收集
每一个类或接口的常量池的运行时表示形式,它包括了若干种不同的常量:从编译期可知的数值字面量到必须运行期解析后才能获得的方法或字段引用
每一个运行时常量池都分配在Java虚拟机的方法区之中,在类和接口被加载到虚拟机后,对应的运行时常量池就被创建出来
如果支持本地方法栈,则会在线程创建的时候按线程分配
用来存储数据和部分过程结果的数据结构,同时也被用来处理动态链接、方法返回值和异常分派
栈帧随着方法调用而创建,随着方法结束而销毁,无论方法是正常完成还是异常完成
栈帧的存储空间分配在Java虚拟机栈
每一个栈帧都有自己的局部变量表、操作数栈和指向当前方法所属的类的运行时常量池的引用
栈帧容量的大小仅仅取决于Java虚拟机的实现和方法调用时可被分配的内存
一条线程只有正在执行某个方法的栈帧是活动的,叫做当前栈帧,对应的方法叫当前方法,定义这个方法的类叫当前类。对局部变量表和操作数栈的各种操作,通常指的是当前栈帧进行的操作
栈帧是线程本地私有的数据,不可能在一个栈帧之中引用另外一条线程的栈帧 如果当前方法调用了其他方法,或者当前方法执行结束,那这个方法的栈帧就不再是当前栈帧了。当一个新的方法被调用,则会新建一个栈帧并成为当前栈帧,当方法返回时会将结果(当前新的栈帧)返回给上一个栈帧,当前栈帧丢弃,上一个栈帧重新成为当前栈帧。
长度由编译期决定,存储于类和接口的二进制表示之中,既通过方法的Code属性保存及提供给栈帧使用
当方法被调用时候,参数将会传递至从0开始的连续的局部变量表里。如果是实例方法被调用则第0个局部变量一定是this
局部变量使用索引来进行定位访问,0-max long和double这种需要两个局部变量的类型,索引取最小的那个局部变量。
同局部变量表,长度由编译期决定,存储于类和接口的二进制表示之中,既通过方法的Code属性保存及提供给栈帧使用
操作数栈所属的栈帧在刚刚被创建的时候,操作数栈是空的。
Java虚拟机提供一些字节码指令来从局部变量表或者对象实例的字段中复制常量或变量值到操作数栈中,也提供了一些指令用于从操作数栈取走数据、操作数据和把操作结果重新入栈。在方法调用的时候,操作数栈也用来准备调用方法的参数以及接收方法返回结果,例子参考初识jvm指令执行流程
一个long或者double类型的数据会占用两个单位的栈深度,其他数据类型则会占用一个单位深度
在Class文件里面,描述一个方法调用了其他方法,或者访问其成员变量是通过符号引用来表示的,动态链接的作用就是将这些符号引用所表示的方法转换为实际方法的直接引用
类加载的过程中将要解析掉尚未被解析的符号引用,并且将变量访问转化为访问这些变量的存储结构所在的运行时内存位置的正确偏移量
由于动态链接的存在,通过晚期绑定(Late Binding)使用的其他类的方法和变量在发生变化时,将不会对调用它们的方法构成影响
Java虚拟机采纳了《IEEE Standard for Binary Floating-Point Arithmetic》(ANSI/IEEE Std. 754-1985,New York)浮点算法规范中的部分子集
<init>
在Java虚拟机层面上,Java语言中的构造函数是以一个名为<init>
的特殊实例初始化方法的形式出现的
<init>
这个方法名称是由编译器命名的,因为它并非一个合法的Java方法名字,不可能通过程序编码的方式实现。实例初始化方法只能在实例的初始化期间,通过Java虚拟机的invokespecial
令来调用,只有在实例正在构造的时候,实例初始化方法才可以被调用访问<clinit>
类或者接口是通过<clinit>
方法完成初始化的,这个名字也是由编译器命名的,没有任何虚拟机字节码指令可以调用这个方法,只有在类的初始化阶段中会被虚拟机自身调用抛异常的本质实际上是程序控制权的一种即时的、非局部(Nonlocal)的转换——从异常抛出的地方转换至处理异常的地方
由Java虚拟机执行的每一个方法都会配有零至多个异常处理器(Exception Handlers),异常处理器描述了其在方法代码中的有效作用范围(通过字节码偏移量范围来描述)、能处理的异常类型以及处理异常的代码所在的位置 当有异常被抛出时,Java虚拟机会搜索当前方法的包含的各个异常处理器,如果能找到可以处理该异常的异常处理器,则将代码控制权转向到异常处理器中描述的处理异常的分支之中
Java虚拟机的指令由一个字节长度的、代表着某种特定操作含义的操作码(Opcode)以及跟随其后的零至多个代表此操作所需参数的操作数(Operands)所构成。虚拟机中许多指令并不包含操作数,只有一个操作码。
如果忽略异常处理,那Java虚拟机的解释器使用下面这个伪代码的循环即可有效地工作:
12345 | do { 自动计算PC寄存器以及从PC寄存器的位置取出操作码; if (存在操作数) 取出操作数; 执行操作码所定义的操作} while (处理下一次循环); |
---|
如果要将一个16位长度的无符号整数使用两个无符号字节存储起来,如下所示
(byte1 << 8) | byte2
对两个操作数栈上的值进行某种特定运算,并把结果重新存入到操作栈顶
将两种Java虚拟机数值类型进行相互转换
注意:
各种类型的比较最终都会转化为int类型的比较操作: boolean类型、byte类型、char类型和short类型的条件分支比较操作,都使用int类型的比较指令来完成。而对于long类型、float类型和double类型的条件分支比较操作,则会先执行相应类型的比较运算指令,运算指令会返回一个整形值到操作数栈中,随后再执行int类型的条件分支比较操作来完成整个分支跳转
在程序中显式抛出异常的操作会由athrow指令实现,除了这种情况,还有别的异常会在其他Java虚拟机指令检测到异常状况时由虚拟机自动抛出
Java虚拟机可以支持方法级的同步和方法内部一段指令序列的同步