之前提到java内存布局的时候提到过虚拟机栈,其中虚拟机栈里存储的元素就是栈帧。栈帧存储了局部变量表,操作数栈,动态连接和方法返回地址等信息,每一方法从调用开始至执行结束的过程,就是虚拟机栈中的一个栈帧从入栈到出栈这个过程。
上图展示的就是一个线程内的栈帧中的具体内容,接下来将会具体讲述栈帧中的具体区域。
局部变量表是一组变量值存储空间,存储方法的参数和方法内部定义的局部变量。局部变量表中的最小存储单位为容量槽,32位虚拟机中,一个容量槽能存储的类型为int,short,boolean,float,byte,char,reference,returnAddress类型,前六种就java基本类型,reference是对象实例的引用类型,至于最后一个存储指向一些字节码的地址,已经很少用到。在64位虚拟机中,根据高低位分配两个连续的容量槽来存储,新增了long,double两种类型。
局部变量表中的变量不像类变量一样存在两个阶段赋值,第一阶段准备赋初始值,第二阶段赋值用户定义的值,局部变量在初始化阶段创建好就赋上用户定义的值。
又称操作栈,先进后出的数据结构,栈的深度在编译期间就已确定好。主要就是根据字节码指令进行出入栈的操作。
每个栈帧中包含一个指向运行时常量池中的该方法的引用,持有这引用就是为了在方法中支持动态连接。之前提到过class的常量池中存有大量符号引用,这些符号引用在类加载或第一次使用转为为直接引用称为静态引用,而在运行期每次都转为直接引用称为动态引用。
方法执行后,退出方式只有两种
指规范中没有提到的一些附加信息,如调试,性那相关信息等,该附加信息由各自的jvm实现机实现。
方法调用并不是说具体发方法执行过程,方法调用的唯一任务就是确认被调用方法的版本(即确定调用哪个方法)
之前提到过符号引用转换为直接引用有静态连接和动态连接,所以确定具体执行调用某个方法就变的相对复杂。
即编译期可知,运行期不变,在加载阶段,即将常量池的符号引用转为直接引用,符合这类条件的方法就只有静态方法和私有方法,前者与类型关联,后者外部不可访问,所以这两方法的无法通过继承或别的方式重写出其他版本,所以放在加载阶段转化直接引用没有问题。
分派指方法的调用,分派是多态性的表现。
静态分派指重载,并不是常说的静态语义,分派本身就是动态性的体现。
public class Test {
static abstract class Human {
}
static class Man extends Human {
}
static class Woman extends Human {
}
public void sayHello(Human guy) {
System.out.println("hello,guy!");
}
public void sayHello(Man guy) {
System.out.println("hello,gentleman!");
}
public void sayHello(Woman guy) {
System.out.println("hello,lady!");
}
public static void main(String[] args) {
Human man = new Man();
Human woman = new Woman();
Test sr = new Test();
sr.sayHello(man);
sr.sayHello(woman);
}
}
执行结果有经验的读者知道
hello,guy!
hello,guy!
把main方法中的Human称为静态类型,或叫外观类型,对应的
Man类型称为实际类型。静态类型是确定可知的,而实际类型是真正运行期才知道调用的,在编译期间,就确定静态类型Human,所已重载方法就选择了sayHello(Human guy)
,这就是静态分派。
所有依赖静态类型决定的方法执行的分派动作,都称为静态分派,最常用的就是重载。
动态分派代表着多态的另一重要特性重写。
同样,先看个小例子
static abstract class Human {
protected abstract void sayHello();
}
static class Man extends Human {
@Override
protected void sayHello() {
System.out.println("man say hello");
}
}
static class Woman extends Human {
@Override
protected void sayHello() {
System.out.println("woman say hello");
}
}
public static void main(String[] args) {
Human man = new Man();
Human woman = new Woman();
man.sayHello();
woman.sayHello();
man = new Woman();
man.sayHello();
}
运行结果也简单易懂
man say hello
woman say hello
woman say hello
动态分派了解过静态分派,有过多态性的基础读者都很清楚。其jvm实现的过程如下
再看个例子
static class Father {
public int money = 1;
public Father() {
money = 2;
showMeTheMoney();
}
public void showMeTheMoney() {
System.out.println("I am Father, i have $" + money);
}
}
static class Son extends Father {
public int money = 3;
public Son() {
money = 4;
showMeTheMoney();
}
public void showMeTheMoney() {
System.out.println("I am Son, i have $" + money);
}
}
public static void main(String[] args) {
Father gay = new Son();
System.out.println("This gay has $" + gay.money);
}
运行结果需要读者思考一番
I am Son, i have $0
I am Son, i have $4
This gay has $2
这是因为字段不支持多态性。这段代码的执行过程可以屡一下
Father gay = new Son();
方法,子类构造方法会调用父类的构造方法。public int money = 3;
进行初始化public Father()
构造方法,执行money = 2;
进行赋值。showMeTheMoney();
方法,但此时实际类型为Son,所以会走进Son类的public void showMeTheMoney()
I am Son, i have $0
public Father()
后,走进Son的构造方法,重复之前2-5的步骤方法的接受者与方法的参数统称为方法的宗量,根据分派基于宗量的选择,可以判断为单分派还是多分派。一个宗量对方法选择的是单分派,多个宗量对方法选择的是多分派,可能有些难以理解,看个例子
static class QQ {}
static class _360 {}
public static class Father {
public void hardChoice(QQ arg) {
System.out.println("father choose qq");
}
public void hardChoice(_360 arg) {
System.out.println("father choose 360");
}
}
public static class Son extends Father {
public void hardChoice(QQ arg) {
System.out.println("son choose qq");
}
public void hardChoice(_360 arg) {
System.out.println("son choose 360");
}
}
public static void main(String[] args) {
Father father = new Father();
Father son = new Son();
father.hardChoice(new _360());
son.hardChoice(new QQ());
}
返回结果也直白
father choose 360
son choose qq
解析一下
静态分派father.hardChoice(new _360());
中确定执行哪个方法的因素,一个是静态类型,是Father还是子类Son,另一个是参数类型_360,还是QQ,所以会在常量池产生两个字符引用指向Father类的下两个hardChoice方法,由两个宗量确定,所以可知java 的静态是多分派。
接下来看重写,动态分派拿到的类型是实际类Son,执行方法的参数son.hardChoice(new QQ());
编译期间就可以确定,所以就一个宗量,是单派的。
所以得到的结论是java 的静态分派是多分派,动态分派为单分派。
本文详细了介绍了栈帧结构以及方法的执行过程,包括多态性的介绍和实现。