JVM字节码执行引擎和动态绑定原理
1.执行引擎
- 全部Java虚拟机的执行引擎都是一致的: 输入的是字节码文件,处理过程就是解析过程,最后输出执行结果。
- 在整个过程不一样的数据在不一样的结构中进行处理。
2.栈帧
- jvm进行方法调用和方法执行的数据结构,是虚拟机栈的元素。
(图片来自网络)
- 栈帧包括:局部变量表、操做数栈、动态连接、方法返回地址等
- 编译期就肯定了须要多大的局部变量表,多深的操做数栈,这些信息全在字节码中。
- 只有位于栈顶的栈帧才有效,称为当前栈帧,所对应的方法就是当前正在执行的方法。
2.1 局部变量表
- 容量以变量槽slot为单位,slot内存大小随着需求而变化而且不固定。
- 方法执行时jvm使用局部变量表完成参数值到参数列表的传递过程。
- slot能够被其余变量复用。
- 局部变量不存在准备阶段,因此不会赋予系统初始值,若是不初始化那么他就不能使用。
- jvm经过索引定位的方式定位slot,从0到最大slot数量。0位索引slot是用来存放方法所属对象实例的引用(this),以后按参数列表分配slot,再后按照局部变量顺序分配slot。
2.2操做数栈
- 方法刚刚开始执行时为空,在执行过程当中会有各类字节码指令往操做数栈中写入和提取内容,也就是入栈/出栈操做。
- 概念上两个栈帧相互独立,但大多数虚拟机会存在一些优化,让下面栈帧的操做数栈部分区域和上面栈帧的部分局部变量表重合,成为共享区域。
2.3动态连接
- 保存 指向运行时常量池中 该栈帧所属方法的 引用。
- 做用就是 运行期间将符号引用转化为直接引用。
2.4方法返回地址
3.方法的调用,动态绑定过程。
- 编译过程不包括传统的链接,class文件中只是存符号引用,而不是直接引用。
- 方法调用就是肯定该方法是哪一个类的哪一个方法换个说法就是绑定,这不一样于方法执行。
-
解析
- 符号引用转化为直接引用,由于编译器并不知道分配内存后某个变量具体内存位置,因此编译时使用一个符号代替,分配内存后该变量所在的内存位置就等于这个符号,解析阶段找到这个符号对应的内存位置而后指向她,这就是符号引用转化为直接引用。
- 类加载的解析阶段会把 部分符号引用转化为直接引用,但这种解析的前提条件就是:方法在程序执行以前就有一个就能够肯定的版本,而且在运行期间不可变。
- 编译期可知运行期不可变这种方法有static和private方法,想一想也是,这两种方法只有定义他们的类才能调用,他们一出生就肯定了惟一的主人。他们不可能被继承或者重写其余版本。
- 实例构造器,父类方法这2中也能肯定惟一的版本,因此这些方法也是在类加载阶段解析为直接引用。
- 静态方法,私有方法,实例构造器,父类方法这些方法可肯定惟一版本因此统称为 非虚方法,final方法也是非虚方法。
-
分派调用
- 分派调用就是多态特征的体现,在重载重写的方法中正确肯定版本。
- 静态类型和动态类型
- Human man = new Man();
- Human 就是变量man 的静态类型, Man 就是man 的实际类型。
- 静态类型在编译期可知,动态类型在运行期才能肯定
- 静态分派:根据参数的静态类型肯定该方法使用的版本
- 方法被重载了好几个版本,肯定要使用哪个版本就是经过当前调用方法传入参数的静态类型肯定版本。
- 静态分派会找到一个和参数静态类型最适合的版本,如 int 类型传入多个没有int的重载版本中还会找到float版本。
- 静态分派在编译期肯定调用哪个版本,并把符号引用记录下来。
- 注意静态分派只肯定了方法的版本,若是一个类没有继承其余类当前方法也不是覆写了父类方法,那么他就只有这一个版本可用,若是重写了父类方法,那么就由动态分派来肯定使用哪一个重写方法。
- 动态分派:运行期间根据调用方法的对象的实际类型肯定使用哪一个类的方法,也就是肯定使用哪一个重写方法。编译期间没法肯定实际类型
- 找到调用当前方法的对象的实际类型
- 在实际类型中由静态分派肯定方法版本。
- 若是找到对应版本则检查访问权限若是经过则返回此方法的引用。
- 若是找不到则从下往上在父类中找,若是找不到则抛出异常。
- 单分派多分派
- 方法的接收者和参数统称为综量
- 静态分派是根据方法参数肯定的,方法参数能够有多个因此是多个宗量,属于多分派。
- 动态分派是根据一个实际类型肯定的因此属于单宗量,因此是单分派。
- Java 目前是一门静态多分派,动态单分派的语言。
- 静态分派和动态分派的区别
- 静态分派经过参数类型查找重载版本,而且是参数的静态类型。
- 动态分派经过调用方法的对象查找重写版本,而且是对象的实际类型。
4. 字段的调用和方法的调用不一样
- 当一个引用变量它的编译时的类型和运行是的类型不同时:
- 经过引用调用字段则调用的是 编译时类型(静态类型 )对象的字段,也就是父类的字段。
- 经过引用调用方法则是经过动态分派调用实际类型的方法,若是没有就去父类中查找。
欢迎关注本站公众号,获取更多信息