Java虚拟机规范制定了Java字节码执行引擎的概念模型,Java执行引擎作用概括起来就是执行编译产生的Java class文件,为用户提供了底层OS的调用,屏蔽了不同平台硬件和OS的差异性,使得编写的代码无差别的在各个平台运行;对于Java字节码执行一般有解释执行和编译执行两种,具体使用哪种看jvm的实现;
JVM运行时内存结构
- 运行时栈帧
- 局部变量表---方法的局部变量的值
- 操作数栈--方法运行时子节码指令的操作参数
- 动态链接--方法运行时在栈帧中保存该方法在运行时常量池的引用,这个引用可以理解为指令计数器用来寻找运算指令的位置;
- 方法返回地址信息
Java中一个方法的执行对应一个栈帧在虚拟机栈中从入栈到出栈的过程;首先看一下局部变量表的具体结构;
局部变量表中存放的是方法参数和方法内部定义的局部变量;局部变量表以变量槽为基本单位来存放方法变量;局部变量的大小在编译的时候就能确定,看一段代码--
main()方法中定义了五个变量,那么运行时main方法的局部变量表中存放的是什么呢?以及为什么局部变量表大小在编译器就能确定大小呢?
这里有两点注意:
- 基本类型和引用类型的区别---对于a,b,c三个基本类型,局部变量表中存放的就是基本类型的值,而对于引用类型 数组和链表,局部变量表中存放的是其引用地址---直接或间接内存地址;
- 对于基本类型bc,Java中规范了是64位存储,如果对于32位系统来说,这两个变量应该使用连续的两个变量槽来存放,-----long和double的非原子性协定;
JVM使用访问局部变量表的时候使用索引来定位槽的位置,此时如果执行的是实例方法,则局部变量表中第一个槽索引为0的位置存放的是该实例对象的引用----实例方法中通过this访问该隐含参数---也就是说,this指针是作为实例方法的隐含参数传递的;
方法调用
- 解析调用
解析调用是指在类加载的解析阶段就可以把该方法的符号引用解析为方法的直接引用
这类方法有静态方法(类方法),私有方法,实例构造器,父类方法,被final修饰的方法 - 分派调用
- 静态分派--依赖静态类型决定方法重载版本,静态分派在编译期确定---用于方法重载
package test2;public class A {static class B extends A{}static class C extends A{}public void sayName(A a){System.out.println("A");}public void sayName(C C){System.out.println("A");}public void sayName(B B){System.out.println("B");}public static void main(String[]args){A c=new C();A b=new B();new A().sayName(c);new A().sayName(b);} }
上边代码中sayName()传了不同的动态类型c和b,但是c和b的静态类型都是A;所以Java中对重载的实现是通过静态分派来实现的,只根据静态类型判断方法的调用;
- 动态分派--用于方法重写,动态分派通过栈帧中的this对象引用来实现;
- 静态分派--依赖静态类型决定方法重载版本,静态分派在编译期确定---用于方法重载
- 动态分派的实现---方法区中建立虚方法表
- 虚方法表中存放方法的实力入口地址
- 子类中没有重写父类方法的时候子类方法入口地址和父类方法地址入口是一样的
- 子类中发生了重写,则方法入口指向子类的实现方法地址