大神链接:作者有幸结识技术大神孙哥为好友,获益匪浅。现在把孙哥视频分享给大家。
孙哥链接:孙哥个人主页
作者简介:一个颜值99分,只比孙哥差一点的程序员
本专栏简介:话不多说,让我们一起干翻JVM本文章简介:话不多说,让我们讲清楚JVM当中与操作数栈相关的动态链接和常量池的作用
文章目录
一:方法的调用
1:概述
2:静态链接
3:动态链接
二:方法的绑定
1:绑定概念
2:早期绑定
3:晚期绑定
三:晚期绑定示例
1:编写代码
2:jclasslib查看内容
四:早期绑定示例
1:编写代码
2:jclasslib查看内容
五:总结说明
一:方法的调用
我们每天都在写方法的调用,但是我们能搞明白其中的原理和JVM当中的操作步骤么?这就是本文的意义。
1:概述
官方说法:
在JVM中,将符号引用转换为调用方法的直接引用这个操作是跟JVM当中方法的绑定机制息息相关的。
说人话:
上边这段话是什么意思?我这里给大家解释一下,我们javap整理完毕字节码文件之后,我们会可以在任意一个方法中查看code下的字节码指令,很多字节码指令的后边都会跟#数字这么一个概念,这个就是符号引用,这个引用指向常量池。
所谓将符号引用转换为方法的直接引用,就是将这个字节码指令后边的符号引用,转变为真实的方法。
下列中的#3就是符号引用。
public void methodB();descriptor: ()Vflags: (0x0001) ACC_PUBLICCode:stack=3, locals=1, args_size=10: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;3: ldc #6 // String methodB().....5: invokevirtual #5 // Method java/io/PrintStream.println:(Ljava/lang/String;)V8: aload_09: invokevirtual #7 // Method methodA:()V12: aload_013: dup14: getfield #2 // Field num:I17: iconst_118: iadd19: putfield #2 // Field num:I22: return
从上述找一个例子的话,就是将偏移地址为9的字节码指令后边的#7这个符号引用用真实的方法字面量代替
2:静态链接
官方说法:
当一个字节码文件被装载进JVM内部时,如果被调用的目标方法在编译期可知且运行期保持不变时。这种情况下将调用方法的符号引用转换为直接引用的过程称之为静态链接。
说人话:
静态链接:这种方式在编译阶段就已经把符号引用直接转换为了直接引用。
3:动态链接
官方说法:
如果被调用的方法在编译期无法被确定下来,也就是说,只能够在程序运行期将调用方法的符号引用转换为直接引用,由于这种引用转换过程具备动态性,因此也就被称之为动态链接。
说人话:
动态链接:这种方式在运行阶段才能把符号引用直接转换为直接引用。
二:方法的绑定
1:绑定概念
绑定是一个字段、方法或者类在符号引用被替换为直接引用的过程,这仅仅发生一次。这个不论是编译器确定还是运行期确定都只会发生一次,不会修改。
对应的方法的绑定机制为:早期绑定 (Early Bindng)和晚期绑定(Late Binding)。
2:早期绑定
官方说法:
早期绑定就是指被调用的目标方法如果在编译期可知,且运行期保持不变时即可将这个方法与所属的类型进行绑定,这样一来,由于明确了被调用的目标方法究竟是哪一个,因此也就可以使用静态链接的方式将符号引用转换为直接引用。
说人话:
早期绑定是和我们的静态绑定相对应的。
3:晚期绑定
官方说法:
如果被调用的方法在编译期无法被确定下来,只能够在程序运行期根据实际的类型绑定相关的方法,这种绑定方式也就被称之为晚期绑定
说人话:
晚期绑定是和我们的动态绑定相对应的。
三:晚期绑定示例
1:编写代码
class Animal {public void eat(){System.out.println("动物进食");}
}interface Huntable{void hunt();
}class Dog extends Animal implements Huntable{@Overridepublic void eat(){System.out.println("狗吃骨头");}@Overridepublic void hunt() {System.out.println("捕食耗子,多管闲事");}
}class Cat extends Animal implements Huntable{@Overridepublic void eat(){System.out.println("猫吃鱼");}@Overridepublic void hunt() {System.out.println("捕食耗子,天经地义");}
}public class AnimalTest{public void showAnimal(Animal animal){animal.eat();//晚期绑定}public void showHunt(Huntable h){h.hunt();//晚期绑定}}
2:jclasslib查看内容
四:早期绑定示例
1:编写代码
class Animal {public void eat(){System.out.println("动物进食");}
}interface Huntable{void hunt();
}class Dog extends Animal implements Huntable{@Overridepublic void eat(){super.eat();//早期绑定System.out.println("狗吃骨头");}@Overridepublic void hunt() {System.out.println("捕食耗子,多管闲事");}
}class Cat extends Animal implements Huntable{public Cat(){super();//早期绑定}public Cat(String name){this();//早期绑定}@Overridepublic void eat(){System.out.println("猫吃鱼");}@Overridepublic void hunt() {System.out.println("捕食耗子,天经地义");}
}public class AnimalTest{public void showAnimal(Animal animal){animal.eat();//晚期绑定}public void showHunt(Huntable h){h.hunt();//晚期绑定}}
2:jclasslib查看内容
光标放到cat这个类上查看他的jclasslib
invokeSpecial是早期绑定字节码指令,invokevirtual是晚期绑定的字节码指令。
五:总结说明
随着高级语言的横空出世,类似于Java一样的基于面向对象的编程语言如今越来越多,尽管这类编程语言在语法风格上存在一定的差别,但是它们彼此之间始终保持着一个共性,那就是都支持封装、继承和多态等面向对象特性
既然这一类的编程语言具备多态特性,那么自然也就具备早期绑定和晚期绑定两种绑定方式。
Java中任何一个普通的方法其实都具备虚函数的特征,也就是运行期才能确定下来,它们相当于c++语言中的虚函数 (c++中则需要使用关键字virtual来显式定义)。
如果在Java程序中不希望某个方法拥有虚函数的特征时,则可以使用关键字final来标记这个方法。也就是一个方法不想被晚期绑定,直接把他给final修饰即可。