JVM_03 运行时数据区1_Stack_动态链接机制_Dynamic Linking

动态链接(Dynamic Linking)
  • 每一个栈帧内部都包含一个指向运行时常量池或该栈帧所属方法的引用。包含这个引用的目的就是为了支持当前方法的代码能够实现动态链接。比如invokedynamic指令
  • 在Java源文件被编译成字节码文件中时,所有的变量和方法引用都作为符号引用(symbolic Refenrence)保存在class文件的常量池里。比如:描述一个方法调用了另外的其他方法时,就是通过常量池中指向方法的符号引用来表示的,那么动态链接的作用就是为了将这些符号引用转换为调用方法的直接引用。

在这里插入图片描述
为什么需要常量池呢
常量池的作用,就是为了提供一些符号和常量,便于指令的识别。
在这里插入图片描述

在这里插入图片描述

2.5.1方法的调用

在JVM中,将符号引用转换为调用方法的直接引用与方法的绑定机制相关

静态链接

  • 当一个 字节码文件被装载进JVM内部时,如果被调用的目标方法在编译期可知,且运行期保持不变时。这种情况下将调用方法的符号引用转换为直接引用的过程称之为静态链接。

动态链接

  • 如果被调用的方法在编译期无法被确定下来,也就是说,只能够在程序运行期将调用方法的符号引用转换为直接引用,由于这种引用转换过程具备动态性,因此也就被称之为动态链接。

对应的方法的绑定机制为:早起绑定(Early Binding)和晚期绑定(Late Bingding)。绑定是一个字段、方法或者类在符号引用被替换为直接引用的过程,这仅仅发生一次。

早期绑定

  • 早期绑定就是指被调用的目标方法如果在编译期可知,且运行期保持不变时,即可将这个方法与所属的类型进行绑定,这样一来,由于明确了被调用的目标方法究竟是哪一个,因此也就可以使用静态链接的方式将符号引用转换为直接引用。

晚期绑定

  • 如果被调用的方法在编译期无法被确定下来,只能够在程序运行期根据实际的类型绑定相关的方法,这种绑定方式也就被称之为晚期绑定。

随着高级语言的横空出世,类似于java一样的基于面向对象的编程语言如今越来越多,尽管这类编程语言在语法风格上存在一定的差别,但是它们彼此之间始终保持着一个共性,那就是都支持封装,集成和多态等面向对象特性,既然这一类的编程语言具备多态特性,那么自然也就具备早期绑定和晚期绑定两种绑定方式。
Java中任何一个普通的方法其实都具备虚函数的特征,它们相当于C++语言中的虚函数(C++中则需要使用关键字virtual来显式定义)。如果在Java程序中不希望某个方法拥有虚函数的特征时,则可以使用关键字final来标记这个方法。

2.5.2虚方法和非虚方法

子类对象的多态性使用前提:①类的继承关系②方法的重写

  • 非虚方法

    • 如果方法在编译器就确定了具体的调用版本,这个版本在运行时是不可变的。这样的方法称为非虚方法
    • 静态方法、私有方法、final方法、实例构造器、父类方法都是非虚方法
  • 其他方法称为虚方法

虚拟机中提供了以下几条方法调用指令:
普通调用指令

  • 1.invokestatic:调用静态方法,解析阶段确定唯一方法版本;
  • 2.invokespecial:调用方法、私有及弗雷方法,解析阶段确定唯一方法版本;
  • 3.invokevirtual调用所有虚方法;
  • 4.invokeinterface:调用接口方法;

动态调用指令:

  • 5.invokedynamic:动态解析出需要调用的方法,然后执行 .

前四条指令固化在虚拟机内部,方法的调用执行不可人为干预,而invokedynamic指令则支持由用户确定方法版本。其中invokestatic指令和invokespecial指令调用的方法称为非虚方法,其余的(final修饰的除外)称为虚方法。

/**
 * 解析调用中非虚方法、虚方法的测试
 */
class Father {
    public Father(){
        System.out.println("Father默认构造器");
    }

    public static void showStatic(String s){
        System.out.println("Father show static"+s);
    }

    public final void showFinal(){
        System.out.println("Father show final");
    }

    public void showCommon(){
        System.out.println("Father show common");
    }

}

public class Son extends Father{
    public Son(){
        super();
    }

    public Son(int age){
        this();
    }

    public static void main(String[] args) {
        Son son = new Son();
        son.show();
    }

    //不是重写的父类方法,因为静态方法不能被重写
    public static void showStatic(String s){
        System.out.println("Son show static"+s);
    }

    private void showPrivate(String s){
        System.out.println("Son show private"+s);
    }

    public void show(){
        //invokestatic
        showStatic(" 大头儿子");
        //invokestatic
        super.showStatic(" 大头儿子");
        //invokespecial
        showPrivate(" hello!");
        //invokespecial
        super.showCommon();
        //invokevirtual 因为此方法声明有final 不能被子类重写,所以也认为该方法是非虚方法
        showFinal();
        //虚方法如下
        //invokevirtual
        showCommon();//没有显式加super,被认为是虚方法,因为子类可能重写showCommon
        info();

        MethodInterface in = null;
        //invokeinterface  不确定接口实现类是哪一个 需要重写
        in.methodA();

    }

    public void info(){

    }

}

interface MethodInterface {
    void methodA();
}

关于invokedynamic指令

  • JVM字节码指令集一直比较稳定,一直到java7才增加了一个invokedynamic指令,这是Java为了实现【动态类型语言】支持而做的一种改进
  • 但是java7中并没有提供直接生成invokedynamic指令的方法,需要借助ASM这种底层字节码工具来产生invokedynamic指令.直到Java8的Lambda表达式的出现,invokedynamic指令的生成,在java中才有了直接生成方式
  • Java7中增加的动态语言类型支持的本质是对java虚拟机规范的修改,而不是对java语言规则的修改,这一块相对来讲比较复杂,增加了虚拟机中的方法调用,最直接的受益者就是运行在java凭条的动态语言的编译器

动态类型语言和静态类型语言

  • 动态类型语言和静态类型语言两者的却别就在于对类型的检查是在编译期还是在运行期,满足前者就是静态类型语言,反之则是动态类型语言。
  • 直白来说 静态语言是判断变量自身的类型信息;动态类型预言师判断变量值的类型信息,变量没有类型信息,变量值才有类型信息,这是动态语言的一个重要特征
  • Java是静态类型语言(尽管lambda表达式为其增加了动态特性),js,python是动态类型语言.
动态链接:指向运行时常量池的方法的引用
为了将符号引用转化为调用方法的直接引用
常量池:为了提高一些符号和常量,便于指令的引用

符号的引用到直接的引用与绑定机制有关.动态绑定和静态绑定
编译期间为静态链接,运行期间为动态链接
invokevirtual:方法调用为晚期绑定
invokespecail:构造器为早期绑定

invokespecail,invokestatic编译期课确定的为非虚方法:静态方法,final,私有方法,实例构造器,父类方法
invokevirtual,invokeinterface其他为虚方法
invokedynamic为lambda表达式提供的动态方法,jdk8以后提供的

猜你喜欢

转载自blog.csdn.net/qq_43141726/article/details/114582094