字节码指令
- 单字节、放弃操作数长度对齐、省略填充和间隔符号、短小精悍
- 面向操作数栈而不是寄存器
- 大多数指令不包含操作数,只有一个操作码
- 常见的指令包括:加载和存储、运算、窄化类型转换、对象创建和访问、操作数栈管理、控制转移、方法调用和返回、异常抛出、同步(使用“管程”monitor)
- 指令其他内容参照字节码执行引擎理解。
栈帧结构
- 虚拟机栈的栈元素
- 每一栈帧对应一个方法,只有栈顶的栈帧才有效
- 方法从调用开始到执行完成——栈帧入栈到出栈
- 存储内容
- 方法的局部变量表
- 最大容量由编译后的class文件中方法的code属性的max_locals决定
- 方法执行时完成参数值到参数变量列表的传递过程
- 实例方法执行时局部变量表第0位默认传递this引用的当前对象实例
- 操作数栈
- 最大容量由编译后的class文件中方法的code属性的max_stacks决定
- 后入先出,起始为空,方法执行过程中写入和提取
- 动态连接
- 一个指向运行时常量池中该栈帧所属方法的引用,支持方法调用过程中的动态连接
- 方法返回地址
- 方法正常返回时,调用者的PC计数器的值
- 方法异常退出时,返回地址通过异常处理器确定,栈帧不保存该信息
- 附加信息
- 方法的局部变量表
- 虚拟机的方法调用(不等同于方法执行)
- 类加载的解析阶段,将符号引用转化为直接引用
- 适用于静态方法(invokestatic)、私有方法、实例构造器、父类方法(invokespecial)、final方法等非虚方法
- “编译期可知,运行期不变”
- 分派(dispatch)
- 静态分派(编译阶段)
- 方法重载(overload)时是通过参数的静态类型(编译期可知)而不是实际类型(运行期才确定)作为判定依据的
- 动态分派(运行阶段)
- 方法重写(overwrite)调用invokevirtual指令
- 找到操作数栈顶的第一个元素所指向的对象的实际类型C
- C中是否存在相符的方法,访问权限校验
- 否则从下往上查找父类
- 虚拟机动态分派的实现:最常用的手段是在方法区中建立虚方法表,代替元数据查找提高性能
- 方法重写(overwrite)调用invokevirtual指令
- 单分派&多分派
- 宗量: 方法的接受者与方法的参数
- 根据一个宗量对目标方法进行选择称为单分派,否则为多分派
- Java语言属于静态多分派,动态单分派
- 静态分派(编译阶段)
- 动态类型语言支持
- 静态语言:Java、C++等在编译期进行类型检查
- 动态语言:python、JavaScript等运行期类型检查,变量无类型,变量值才有类型
- invokedynamic指令(不同于其他方法调用指令,第一个参数不是被调用方法的符号引用)、java.lang.invoke包
- 通过invoke包的Method Handle,Java拥有类似于函数指针或者委托方法别名的工具
- 与reflection API的区别
- reflection是为Java语言服务,模拟Java代码层次的方法调用,包含信息更多,重量级
- MethodHandle为所有Java虚拟机上的语言服务,模拟字节码层次的方法调用,只包含与执行该方法相关的信息,轻量级,支持虚拟机优化
-虚拟机执行引擎:Java虚拟机的组成部分之一
- 输入字节码,处理(字节码解析),输出执行结果
- java的编译时半独立实现的,生成字节码指令流的过程在虚拟机外部进行,解释器在虚拟机内部
- 与物理机的执行引擎相比,虚拟机不需要依赖硬件,可以执行不被硬件支持的指令集格式
- Java编译器输出的指令流基于栈,零地址指令,依赖操作数栈
- 可移植,代码相对紧凑、编译器实现简单、执行速度较慢、完成相同操作的指令数量更多,频繁内存访问
- 基于寄存器的指令集(主流PC即直接支持的指令集架构)依赖寄存器工作
- 受硬件约束,速度快
- Java编译器输出的指令流基于栈,零地址指令,依赖操作数栈
- 基于栈的字节码解释执行(通过解释器执行)
- 编译执行(通过即时编译器产生本地代码执行)
- 类加载的解析阶段,将符号引用转化为直接引用