虚拟机字节码执行

运行时栈帧结构
栈帧是用来支持虚拟机进行方法调用和方法执行的数据结构,它是虚拟机运行时数据区中的虚拟机栈的栈元素。
栈帧存储了方法的局部变量表、操作数栈、动态连接、和方法返回地址等信息。
方法的调用到执行完成对应一个栈帧的入栈到出栈过程。
局部变量表
一组变量值存储空间,用于存放方法参数和方法内部定义的局部变量。
在编译期确定大小,max_locals
单位slot(32位)
八种数据的类型:byte short int char boolean float reference returnAddress
reference(引用类型):对象实例的引用:找到实例对象在堆中初始地址索引;找到对象方法区的类型信息
64位数据类型long double,可用两个连续的slot,原子操作?,局部变量建立在线程的堆栈上不存在线程安全问题,在类加载校验阶段会抛出异常,不允许访问其中的一个,
执行实例方法时,那局部变量表中第0位索引:指向实例对象的应用(可用this调用此引用),从第1位开始分配局部变量Slot,参数表-->再根据内部定义的局部变量顺序+作用域分配其余的Slot
为了节省空间,Slot是可以重用的:场景方法中作用域内的局部变量超出作用域之后,
副作用是影响GC行为,延迟GC,超出作用域后,作用域内局部变量slot未被占用,GC可能不会回收
不使用的对象应手动赋值为null,但是;以恰当的变量作用域来控制变量回收时间才是最优雅的解决方案,“公有设计,私有实现”,赋值为null的操作在经过JIT优化后会被消除,使得设置null变得毫无意义,而且JIT后本地代码GCRoot和解释执行期有很大的区别。
操作数栈
先入后出
编译期确定大小,写入到Code属性的max_stacks数据项,任何时候,操作数栈的深度都不会超过max_stacks
每个栈元素可以是任意类型的java数据类型,32位数据类型栈容量为1,,64为2
方法开始执行时,操作数栈时空的,执行过程中,会有各种字节码指令往操作数栈中写入和提取内容,入栈、出栈操作。
编译期:操作数栈中的元素数据类型必须与字节码指令的序列严格匹配。
概念模型中连个栈帧作为虚拟机栈的元素是完全独立的,但是大多数虚拟机实现都会做一些优化,两栈部分局部变量表和部分操作数栈重叠。
动态链接
每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用。
静态解析:Class文件中常量池存在大量的符号引用,类加载或者第一次使用时转化为直接引用
动态链接:Class文件中常量池存在大量的符号引用,运行期间转化为直接引用
方法返回地址
正常完成出口:执行引擎遇到任意一个方法返回的字节码指令,导致方法退出。
异常完成出口:在本方法的异常表中没有搜索到匹配的异常处理器,导致方法退出。
附加信息
 
方法调用:
方法调用并不等同与方法执行,方法调用阶段唯一任务:确定被调用方法的版本(调用哪一个方法)
解析
类加载的解析阶段,会将一部分符号引用转化为直接引用--编译期可知,运行期不可变
虚拟机提供5条方法调用字节码指令
invokestatic:调用静态方法
invokespecial:调用实例构造器<init>方法,私有方法和父类方法
invokevirtual:调用所有的虚方法
invokeinterface:调用接口方法,会在运行时再确定一个实现此接口的对象。
invokedynamic:现在运行时动态解析出调用点限定符所引用的方法,然后在执行该方法。
其中被invokestatic、invokespecial指令调用的方法,都可以在解析阶段中确定唯一的调用版本:
 
方法调用
静态解析
静态解析成立前提:方法在程序真正运行之前就有一个可确定的调用版本,并且这个方法的调用版本在运行期是不可改变的。即调用目标在程序代码写好,编译器进行编译时必须确定下来的。
符合“编译器克制,运行期不可变”的方法:静态方法,私有方法、实例构造器、父类方法--非虚方法
解析解析一定是一个静态的过程;分派调用则可能是静态的也可能是动态的
分派
Human man = new Man();

将Human称为变量man的静态类型

将Man称为变量man的实际类型

静态分派
所有以来静态类型来确定方法执行版本的分派过程称为静态分派。
静态分派的典型应用就是方法重载。                          
静态分派发生在编译阶段,因此确定静态分派的动作实际上不是有虚拟机来执行的。
重载版本并不唯一,往往只能确定一个更加合适的版本,产生模糊结论的只要原因是字面量不需要定义,即字面量没有显示的静态类型,只能根据语言的规则去理解和推断。
public static void sayHello(char arg){
    System.out.println("hello char");
}

public static void sayHello(Character arg){
    System.out.println("hello Character ");
}

public static void main(String[] args){
    sayHello('a');//字面量'a'没有显示的静态类型,只能根据语言规则去理解与推断
}
//基本类型及基本类型转换-->封装类型-->序列化类型-->父类-->变长参数
//(char-int-long)-->Character-->Serializable-->Object-->char..

解析和分派并不是二选一的排他关系,静态方法是静态解析的,但静态方法重载后选择合适版本是静态分派的。

动态分派
运行期根据实际类型确定方法执行版本的分派过程称为动态分派。
动态分派的典型应用就是方法重写。 (在继承关系自下向上查找)
 单分派与多分派
方法的接受者与方法的参数统称为方法的宗量
 单分派是根据一个宗量对目标方法进行选择,多分派则是根据多于一个宗量对目标方法进行选择。
Java重载根据静态类型+参数确定目标方法的,即根据两个宗量进行选择属于静态多分派。
Java重写根据实际类型来确定目标方法的,即根据一个宗量进行选择属于动态单分派。
public class Dispatch{
    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[] arg){
        Father father = new Father();
        Father son = new Son();
        father.hardChoise(new _360());//(重载)编译阶段根据静态类型father+参数_360确定目的方法,静态多分派
        son.hardChoise(new QQ());//由于在编译阶段时已经确定了father.hardChoise(QQ),运行阶段只需要根据实际类型Son就可以确定目的方法son.hardChoise(QQ),动态单分派
    }
}
Java语言是一门静态多分派,动态单分派的语言
 
动态类型支持,典型的动态语言JavaScript,javaScript,类型检查在运行期。
字节码生成技术与动态代理的实现
字节码生成技术,常见的CGLib,ASM之类的字节码类库,JDK中javac命令就是字节码生成技术的“老祖宗”
现在开发人员都是用过Spring框架,其中AOP的实现方式有两种:JDK自带的动态代理及其拓展类库CGLib(主要解决动态代理方式实现代理时的目标类必须实现接口的缺点)
posted on 2019-02-19 18:46  FFStayF  阅读(219)  评论(0编辑  收藏  举报