本文参考自来自周志明《深刻理解Java虚拟机(第2版)》,拓展内容建议读者能够阅读下这本书。
文字版以下:java
一个存储单位称为一个Slot(32位)缓存
为了让全部数据类型的局部变量都可以存储到局部变量表中而设定了定长的Slot长度ide
局部变量第0位Slot优化
Slot重用:离开了做用域的局部变量占用的Slot能够被重用this
对垃圾回收的影响:离开了做用域的局部变量所在的Slot只有在真的不存在对原有局部变量的引用时,GC才会回收相应的对象实例spa
局部变量和类字段的区别code
生命周期不一样对象
赋初值不一样继承
reference类型索引
一个存储单位称为一个栈容量
为了让全部数据类型的局部变量都可以存储到栈中而设定了定长的栈容量
为了知足方法指令的运行时解析
退出方法的方式
退出方法执行的操做
非虚方法:在类加载的时候就能够将符号引用解析为直接引用的方法,即不存在因覆盖而产生多版本的方法
虚方法:在类加载的时候有可能没法肯定符号引用能够解析为哪一个直接引用的方法
invokestatic
invokespecial
invokevirtual
invokeinterface
invokedynamic
静态分派 Method Overload Resolution
典型应用是方法重载 Overload
invokevirtual 选择的方法版本的符号引用
动态分派
典型应用是方法重写 Override
invokevirtual 选择的方法版本的符号引用
类B的方法m(P)重写了B所继承的类A的方法m(P),实际类型为B,A b = new B(),即静态类型为A的对象b,发生了方法调用b.m(p)
invokevirtual A.m
运行期
在B中寻找与invokevirtual调用的方法符号引用名称和签名一致的方法
找到了的话说明实际类型B中就有了这个方法m(P),而后断定该方法是否知足访问权限
没找到
在B的父类中自下向上寻找与invokevirtual调用的方法符号引用名称和签名一致的方法
找到了的话说明实际类型B的父类中有这个方法m(P),而后断定该方法是否知足访问权限
单分派:根据单个方法宗量进行方法选择
多分派:根据多个方法宗量进行方法选择
稳定优化手段:虚方法表 提早记录避免运行时搜索的手段
非稳定的激进优化手段
源码定义了继承了Human
的Man
和Woman
,关键在于看一下编译器将test.sayHi(human);
、test.sayHi(woman);
、test.sayHi(man);
编译成了什么,也就是说静态阶段编译器重载方法的方法分派是什么样的。
class Human{ } class Man extends Human{ } class Woman extends Human{ } public class DispatchTest { public void sayHi(Human human){ System.out.println("Hi human"); } public void sayHi(Man man){ System.out.println("Hi man"); } public void sayHi(Woman woman){ System.out.println("Hi woman"); } public static void main(String[] args) { Human human = new Human(); Human man = new Man(); Human woman = new Woman(); DispatchTest test = new DispatchTest(); test.sayHi(human); test.sayHi(woman); test.sayHi(man); } }
javap获取到的class字节码解释:
public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: ACC_PUBLIC, ACC_STATIC Code: stack=2, locals=5, args_size=1 0: new #7 // class Human 3: dup 4: invokespecial #8 // Method Human."<init>":()V 7: astore_1 8: new #9 // class Man 11: dup 12: invokespecial #10 // Method Man."<init>":()V 15: astore_2 16: new #11 // class Woman 19: dup 20: invokespecial #12 // Method Woman."<init>":()V 23: astore_3 24: new #13 // class DispatchTest 27: dup 28: invokespecial #14 // Method "<init>":()V 31: astore 4 33: aload 4 35: aload_1 36: invokevirtual #15 // Method sayHi:(LHuman;)V 39: aload 4 41: aload_3 42: invokevirtual #15 // Method sayHi:(LHuman;)V 45: aload 4 47: aload_2 48: invokevirtual #15 // Method sayHi:(LHuman;)V 51: return LocalVariableTable: Start Length Slot Name Signature 0 52 0 args [Ljava/lang/String; 8 44 1 human LHuman; 16 36 2 man LHuman; 24 28 3 woman LHuman; 33 19 4 test LDispatchTest;
这段字节码的表义很清晰,就是建立了类型为Human
的Man
和Woman
的三个类的对象实例,而后在执行test.sayHi(human);
、test.sayHi(woman);
、test.sayHi(man);
的时候都将其编译成了调用方法sayHi:(LHuman;)V
,即参数类型是Human
的sayHi
方法。从这里咱们能够看出来实际上静态编译重载方法的时候的只会使用方法参数的静态类型指定的方法,而实际类型因为在运行时可能会发生变化,没有办法在编译期得到其实际类型,所以采用了这种方式直接肯定方法的分派结果。
源码定义了继承了Human
的Man
和Woman
,关键在于看一下编译器将man.sayHi();
、woman.sayHi();
编译成了什么,也就是说静态阶段编译器对重写方法的方法分派是什么样的。
abstract class Human{ protected abstract void sayHi(); } class Man extends Human{ @Override protected void sayHi() { System.out.println("Hi man"); } } class Woman extends Human{ @Override protected void sayHi() { System.out.println("Hi woman"); } } public class DispatchTest { public static void main(String[] args) { Human man = new Man(); Human woman = new Woman(); DispatchTest test = new DispatchTest(); man.sayHi(); woman.sayHi(); } }
javap获取到的class字节码解释:
public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: ACC_PUBLIC, ACC_STATIC Code: stack=2, locals=4, args_size=1 0: new #2 // class Man 3: dup 4: invokespecial #3 // Method Man."<init>":()V 7: astore_1 8: new #4 // class Woman 11: dup 12: invokespecial #5 // Method Woman."<init>":()V 15: astore_2 16: new #6 // class DispatchTest 19: dup 20: invokespecial #7 // Method "<init>":()V 23: astore_3 24: aload_1 25: invokevirtual #8 // Method Human.sayHi:()V 28: aload_2 29: invokevirtual #8 // Method Human.sayHi:()V 32: return LocalVariableTable: Start Length Slot Name Signature 0 33 0 args [Ljava/lang/String; 8 25 1 man LHuman; 16 17 2 woman LHuman; 24 9 3 test LDispatchTest;
这段字节码建立了类型为Man
和Woman
的两个类的对象实例,而后在执行man.sayHi();
、woman.sayHi();
的时候都将其编译成了调用方法Human.sayHi:()V
,即Human
类型的sayHi
方法。从这里咱们能够看出来实际上静态编译重写方法的时候的只会使用实例对象的静态类型的方法,而实际类型因为在运行时可能会发生变化,没有办法在编译期得到其实际类型,所以采用了这种方式直接肯定方法的分派结果。可是在运行时会去获取这个实例对象的实际类型,而后看这个实际类型是否认了名称和限定符知足的方法,若是有就会选择分派到这个方法上。这就是运行时动态分派产生的影响,这种选择在编译器是看不出来的。