再次复习,jvm的组成三大部分
内存模型+类加载系统+字节码执行引擎
jvm是以方法作为最基本的执行单元,所以当我们讨论字节码执行引擎的时候,我们讨论的是方法具体如何执行。
定义:
局部变量表
操作数栈
动态链接
每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用,持有这个引用时为了支持方法调用过程中的动态连接
返回地址
方法的完成有两种类型,一种是正常调用完成,一种是异常退出。但无论那种方式退出,都必须返回最初方法被调用的位置。因此一般来说,记录主调方法的PC计数器的值可以作为返回地址
方法调用不是指方法中的代码执行,方法调用是指确定调用方法的版本,即调用哪一个方法(因为有方法重写和方法重载的存在)
静态解析
编译期间就能完全确定方法调用的版本,适用于静态方法和私有方法,因为它们都不可能被重写,仍可能被重载
分派是一种描述形式,解析也是一种描述形式,并不排他;同时静态分派由于是编译确定所以也有人把它归入静态解析。
静态分派
public class O{
static class A{}
static class B extends A{}
static class C extends A{}
public void a(A a){
System.out.println("A method");
}
public void a(B b){
System.out.println("B method");
}
public void a(C c){
System.out.println("C method");
}
public static void main(String[] args){
O o = new O();
A b = new B();
A c = new C();
o.a(b);
o.a(c);
}
}
输出
A method
A method
为什么会输出A method?
对多态的深入理解
//A 称为静态类型,编译期确定
//B 称为实际类型,运行时确定
A b = new B();
什么叫做运行时确定呢?
A b = 3>0?new B():new C();
针对方法重载的选择,jvm使用是静态类型。也就是说重载对应的是静态类型,也就是变量声明的类型是什么就是什么
静态分派会根据参数的静态类型和方法接收者的静态类型,两个来决定目标方法。
动态分派
public class DynamicDispatch {
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();
}
}
造成动态分派或者说造成方法重写的根源在于invokevirtual关键字,invokevirtual的执行逻辑:
也就是它在运行期确定接收者的实际类型,所以它会根据方法接收者的实际类型来选择方法版本。invokevirtual只对方法有效,对字段无效。也就是字段永远不参与多态,变量永远不参与多态
单分派与多分派
根据方法的接收者和方法参数称为方法的宗量,单分派是指根据一个宗量确定目标方法,多分派是根据多个宗量确定目标方法。
静态分派属于多分派,因为它关心方法的接收者和方法参数。
动态分派属于单分派,因为它只关心方法的接收者。
动态语言
这里稍微了解一下,动态分派不是动态语言,动态语言是变量的类型检查是运行时进行还是编译时进行。也就是像python,不用声明int a。jdk8之后提出的类型推导或者lambda表达式都为java提供了动态语言的支持。所以java其实式动态语言和静态语言的结合,大部分仍然属于静态语言。
初步的理论知识已经讲解过了,本节准备了一段Java代码,看看在虚拟机中实际是如何执行的。下面准备了四则运算的例子,请看下面代码。
public class CalcTest {
public int calc() {
int a = 100;
int b = 200;
int c = 300;
return (a+b)*c;
}
}
从Java语言的角度来看,这段代码没有任何解释的必要,可以直接使用javap命令看看他的字节码指令,如下所示。
javap提示这段代码需要深度为2的操作数栈和4个Slot的局部变量空间,根据这些信息画了下面共7张图,用他们来描述上面执行过程中的代码、操作数栈和局部变量表的变化情况。
上面的执行过程仅仅是一种概念模型,虚拟机最终会对执行过程做一些优化来提高性能,实际运行过程不一定完全符合概念模型的描述......更准确地说,实际情况会和上面的字节码进行优化,例如,在HotSpot虚拟机中,有很多以“fast_”开头的非标准字节码指令用于合并、替换输入的字节码以提升解释执行性能,而即时编译器的优化手段更加花样繁多。
不过,我们从这段程序的执行中也可以看出栈结构指令集的一般运行过程,整个运算过程的中间变量都以操作数栈的出栈、入栈为信息交换途径,符合我们在前面分析的特点。
原文:https://www.cnblogs.com/KODGV-H/p/14310528.html