Java虚拟机规范
2020-12-08 11:19:42 10 举报
AI智能生成
Java虚拟机规范
作者其他创作
大纲/内容
class文件格式
ClassFile结构
无符号数:u1,u2,u4,u8类型的无符号数分别占用1,2,4,8个字节
表:由多个无符号数和表组成,类型命名习惯上为`xxx_info`
表:由多个无符号数和表组成,类型命名习惯上为`xxx_info`
常量池中能保存的项类型。它们都是xx_info,由多个无符号和表组成
各种名称的内部表现形式
类和接口的二进制名使用全限定名。全限定名中的包分隔符用'/'(在.java文件中用'.'作包分隔符)
方法名,字段名,成员变量名用非限定名
描述符
字段描述符
字段描述符解释表
描述 int 实例变量的描述符是 I
Object 类型的实例,其描述符是Ljava/lang/Object;
三维数组 double d[][][]类型的实例变量,其描述符为[[[D
Object 类型的实例,其描述符是Ljava/lang/Object;
三维数组 double d[][][]类型的实例变量,其描述符为[[[D
描述符具体格式见《Java虚拟机规范》P62
方法描述符
(参数描述符)返回值描述符
Object m(int i,double d,Thread t)(..}
描述符为∶(IDLjava/lang/Thread;)Ljava/lang/Object;
描述符为∶(IDLjava/lang/Thread;)Ljava/lang/Object;
对于实例方法来说this也被作为参数计算在方法描述符的长度里面,但是this不在方法描述表达,参数this的传递由Java虚拟机调用实例方法时用指令实现
常量池
Java虚拟机代码约束
静态约束
结构化约束
加载、连接与初始化
运行时常量池
Java虚拟机如何从类或接口的二进制表示中得到符号引用
虚拟机启动
Java虚拟机启动时会有怎样的加载,链接和初始化过程
创建和加载
类或接口的二进制表示是如何通过类加载器加载并由此创建类和接口的
链接
描述链接过程
初始化
详述类和接口是怎么被初始化的
绑定本地方法实现
Java虚拟机绑定本地方法的概念
Java虚拟机退出
Java虚拟机退出的时机
一、Java虚拟机结构
class文件
数据类型
原始类型与值
(也称原生类型或基本类型)
(也称原生类型或基本类型)
数值类型
整数类型
byte,short,char,int,long
char零值是null,其余零值是0
char零值是null,其余零值是0
浮点类型
float,double
零值是0
零值是0
boolean
零值是false
JVM没有直接支持boolean的字节码指令,编译后boolean都被int类型代替
但JVM直接支持boolean类型的数组,boolean数组会被编码为byte数组
JVM没有直接支持boolean的字节码指令,编译后boolean都被int类型代替
但JVM直接支持boolean类型的数组,boolean数组会被编码为byte数组
returnAddress
是指向操作码的指针,操作码和JVM指令对应
是原始类型中唯一不能直接与Java语言的数据类型对应的类型
已经处于名存实亡的状态
是原始类型中唯一不能直接与Java语言的数据类型对应的类型
已经处于名存实亡的状态
引用类型与值
reference
和int,long,double在同一层次
是引用类型的一种
是引用类型的一种
类类型—class type
值指向类实例
数组类型—array type
值指向数组实例
接口类型—interface type
值指向实现了某个接口的类实例或数组实例
引用的值可以为null,表示引用没有指向任何对象
运行时数据区
pc寄存器
每条线程都有一个pc寄存器
当前线程正在执行的方法不是native的,pc寄存器保存的就是JVM正在执行的字节码的地址
如果是native方法,pc寄存器的值就是undefined
当前线程正在执行的方法不是native的,pc寄存器保存的就是JVM正在执行的字节码的地址
如果是native方法,pc寄存器的值就是undefined
Java虚拟机栈
每条线程都有自己私有的Java虚拟机栈,这个栈和线程同时创建你,用于存储栈帧
Java堆
线程共享。虚拟机启动时被创建
方法区
线程共享。保存运行时常量池,字段,方法,构造函数,普通方法的字节码和类,实例,接口初始化时用到的特殊方法
是堆的逻辑组成部分,但此区域可以选择不实现垃圾回收和压缩
是堆的逻辑组成部分,但此区域可以选择不实现垃圾回收和压缩
运行时常量池
class文件常量池表的运行时表现形式
包含从编译器可知的数值字面量到运行解析后才获取的方法或字段的引用
包含从编译器可知的数值字面量到运行解析后才获取的方法或字段的引用
本地方法栈
native方法使用的栈
栈帧
局部变量表
局部变量表保存方法参数列表中的变量和方法体中定义的局部变量的引用
局部变量的索引以0开始计数,double和float占两个局部变量的位置,所以也占两个索引
类的成员方法的局部变量表的第0个局部变量是this,存储调用实例方法的对象的引用
类的成员方法的局部变量表的第0个局部变量是this,存储调用实例方法的对象的引用
操作数栈
程序运行时保存计算得到的中间结果的栈
当前方法所属类的运行时常量池的引用
动态链接
将符号引用转为直接引用
方法调用正常完成
方法调用异常完成(抛出了在方法内未捕获的异常)
对象的表示
Java虚拟机规范不强制规定对象的内部结构应当如何表示
浮点算法
👴看不懂,跳过
特殊方法
实例的初始化方法(构造器)在java虚拟机中的名字是<init>
在虚拟机运行时被invokespecial指令调用
类或接口的初始化方法在java虚拟机中的名字是<clinit>
具有签名多态性的方法(略P18)
异常
抛异常的本质时程序控制权的一种即时的,非局部的转换
异常分同步异常和异步异常
异常出现的原因
athrow字节码指令被执行
产生同步异常
产生异步异常
虚拟机内部的异常也被视为异步异常
抛异常的影响的规范
在异常抛出,程序控制权发生转移的那一刻,所有在异常抛出前执行的字节码指令产生的影响都时可以被观察到的
如果执行的是优化后的代码(指令重排),有些在异常出现位置之后的代码可能已经被执行了,这些优化代码必须保证被提前执行所产生的影响对用户是不可见的
异常处理器表,在4.7.3中讲
字节码指令集简介
数据类型与Java虚拟机
加载和存储指令
用于将数据在栈帧的本地变量表和操作数栈之间来回传递
算术指令
用于对操作数栈中两个数据的计算,并将结果压入操作数栈
类型转换指令
用于Java虚拟机中不同数值类型数据之间的转换
对象的创建与操作
创建对象(new)和创建数组(newarray,anewarray,mutianewarray)使用的字节码是不同的
访问类字段,类实例字段,数组在操作数栈和本地变量表之间的传递,获取数组长度,类实例或数组实例的类型检查和类型转换
访问类字段,类实例字段,数组在操作数栈和本地变量表之间的传递,获取数组长度,类实例或数组实例的类型检查和类型转换
操作数栈管理指令
直接控制操作数栈
控制转移指令
方法调用和返回指令
抛出异常指令
同步指令
二、Java虚拟机编译器
编译器
JDK包括
1. 将Java源代码编译为Java虚拟机的指令集的编译器
2. 实现Java虚拟机的运行时环境
Java编译器还包括JIT(将热点代码编译为机器码的编译器)
但这里只讨论将Java源代码编译为Java虚拟机的指令集的编译器
1. 将Java源代码编译为Java虚拟机的指令集的编译器
2. 实现Java虚拟机的运行时环境
Java编译器还包括JIT(将热点代码编译为机器码的编译器)
但这里只讨论将Java源代码编译为Java虚拟机的指令集的编译器
常量,局部变量和控制结构的使用
常量通常在Class常量池中,有对应的字节码能获取常量池中的常量
局部变量在操作数栈和本地变量表中
控制结构(if,for等)通过字节码中的判断,比较,跳转指令控制
局部变量在操作数栈和本地变量表中
控制结构(if,for等)通过字节码中的判断,比较,跳转指令控制
算术运算
Java对数值类型的变量的算术运算通常在操作数栈中进行
取出操作数栈中的数据并将结果压入栈中
取出操作数栈中的数据并将结果压入栈中
访问运行时常量池
ldc,ldc_w和ldc2_w字节码指令能获取常量池中int,double,long,float类型的数据和表中Stirng实例的引用
接收参数
如果传递了n个参数给某个实例方法,则当前栈帧会按照约定,一参数传递顺序来接收这些参数,将它们保存到方法的第1个至第n个局部变量之中
成员方法的局部变量表的第0个变量是this
静态方法的局部变量表的第0个变量直接保存第一个参数
静态方法的局部变量表的第0个变量直接保存第一个参数
方法调用
invokespecial
静态绑定的方法调用指令
包含实例初始化方法,父类(super)方法,final方法和私有方法
包含实例初始化方法,父类(super)方法,final方法和私有方法
invokevirtual
动态绑定的方法调用指令
invokestatic
调用静态方法,它和invokespecial的区别在于,这个指令不需要this操作数作为参数
而invokespecial需要this操作数作为参数
而invokespecial需要this操作数作为参数
调用方法时使用的指令都需要有一个操作数来确定调用的是哪个方法
如:invokespecial #4
在常量池中#4对应的是方法的符号引用
在运行时,这个符号引用会被转换为调用方法的实际地址(直接引用)
符号引用转为直接引用的过程稍后再说
如:invokespecial #4
在常量池中#4对应的是方法的符号引用
在运行时,这个符号引用会被转换为调用方法的实际地址(直接引用)
符号引用转为直接引用的过程稍后再说
方法句柄的介绍
使用类实例
创建类实例
new指令创建类实例并初始化实例变量的默认值
invokespecial指令可以调用构造方法
通常在调用invokespecial前调dup指令将创建的类实例(reference类型)复制一份再压栈
为什么要复制一份类实例的引用?
栈溢出回答:Because INVOKESPECIAL will consume value created by NEW from the operand stack, but you may need to actually use this value, so the reference is duplicated in advance.
通常在调用invokespecial前调dup指令将创建的类实例(reference类型)复制一份再压栈
为什么要复制一份类实例的引用?
栈溢出回答:Because INVOKESPECIAL will consume value created by NEW from the operand stack, but you may need to actually use this value, so the reference is duplicated in advance.
访问类实例的实例变量
getfield和putfield指令
指令的操作数是类实例的字段,运行时字符引用被替换为直接引用(字段解析中执行的)
这两个指令都需要实例的引用作为参数(看起来就像是通过this.filed访问的字段,即使没加this,编译器也能自动识别出来)
指令的操作数是类实例的字段,运行时字符引用被替换为直接引用(字段解析中执行的)
这两个指令都需要实例的引用作为参数(看起来就像是通过this.filed访问的字段,即使没加this,编译器也能自动识别出来)
数组
编译switch语句
tableswitch和lookupswitch指令支持switch语句
tableswitch下的表中k是有序且递增,lookupswitch下的表中k是无序
所以前者比后者效率高(前者匹配case的方式为范围匹配,匹配速度快)
所以前者比后者效率高(前者匹配case的方式为范围匹配,匹配速度快)
switch-case只支持int类型,但是byte,short,char都使用int的指令集,所以它们也被支持
Java7后也支持String,支持方式为将case后的将字符串常量或字符串字面量替换为int类型的哈希值
Java7后也支持String,支持方式为将case后的将字符串常量或字符串字面量替换为int类型的哈希值
使用操作数栈
Java虚拟机是基于栈的虚拟机
抛出异常和处理异常
异常处理表的from和to是左闭右开区间
编译finally语句块
finally在编译时会添加一个特殊的异常处理器,这个异常处理器可以捕获try快中抛出的所有异常
同步
同步方法的同步由常量池中的ACC_SYNCHRONIZED标志来隐式实现
同步块的同步由monitorenter和monitorexit字节码指令实现
同步块的同步由monitorenter和monitorexit字节码指令实现
monitorenter和monitorexit都需要栈顶一个对象的引用。使用该对象的内部锁完成同步
同步代码块在编译时会添加一个捕获所有异常的异常处理器,执行释放锁的操作,用于当抛出异常时还能保证执行monitorexit释放锁
注解
0 条评论
下一页