深入理解jvm之分派

来源:互联网 发布:程序员怎样创业 编辑:程序博客网 时间:2024/06/07 20:14

之前费了好大劲,一直想搞清楚 .class文件是个什么东东,因为我知道 .java 文件编译后是字节码的二进制文件,所以。。。有点蒙,因为在我的理解中,二进制文件就是文件中只有0和1的文件。。。。其实class文件用十六进制编辑器打开后就是16进制的字符了,那么,其实这是将4个数字合并的结果。.class文件就是字节码文件,用javap指令可以查看到对应的解析。

字节码文件是怎么执行的呢?这个和虚拟机的不同有关,有些是先解释,形成另一种虚拟机的指令,再执行,有些是JIT编译器编译成虚拟机所在主机cpu的本地指令集执行(可是cpu指令集是个什么东东),这样就和物理机打交道了。

粘贴一段文字:

汇编是离机器码最近的一个人类可阅读可编写的语言形式。
机器(CPU)为了运算速度,被设计成只能阅读010101这样的文字的东西。
而人类不可能用0 和 1 ,来写程序,阅读和理解和修改起来太费劲了,效率太低。
所以最开始就有了汇编语言,人类用汇编语言来写人类看得懂的程序,例如:MOV AH,15
之后“编译器”这个程序会把人类编写的程序,翻译成CPU能理解的010101.........
其实编译执行,就是产生本地代码(Native Code)的过程,其实就是生成宿主机处理器的指令集的过程,本地代码并不是0101...,它是指令集。。

Class文件包括java虚拟机指令集和符号表已经若干其他辅助信息(摘自深入理解JVM虚拟机),从这里可以看出javap命查看的字节码指令确实就是Class文件。

扯远了。。。以上只是前提,下面进入讨论的主题。

JVM分派:
方法调用是如何确定调用版本(调用哪一个方法)的呢?
分为静态和动态,静态就是在编译后解析过程就能确定调用版本,动态就是在运行解析时才能确定哪一个版本。

这里其实也是对java的多态有了更深一步的认识,编译期间只知道父类型(或接口类型),直到运行期间才能确定实际类型。

先来看一个例子:

public class Dispatch1{    static abstract class Human    {        }    static class Man extends Human    {        }    static class Woman extends Human{}        public void sayHello(Human guy)    {    System.out.println("hello,guy!");    }    public void sayHello(Man guy)    {    System.out.println("hello,gentleman!");        }    public void sayHello(Woman guy)    {    System.out.println("hello,lady!");    }    public static void main(String[] args){Human man=new Man();Human woman=new Woman();Dispatch1 dp=new Dispatch1();dp.sayHello(man);dp.sayHello(woman);}}

输出:
hello,guy!
hello,guy!
这个其实是重载,Human其实是静态类型,编译期间就可以直到,Man Woman 是实际类型,运行期间才可以确定。重载的时候,是根据参数的静态类型确定的,而不是实际类型。
接下来我们继续来看:


public class SuperTest {public static void main(String[] args) {new Sub().exampleMethod();}}class Super {void interestingMethod() {System.out.println("Super's interestingMethod");}void exampleMethod() {interestingMethod();}}class Sub extends Super {void interestingMethod() {System.out.println("Sub's interestingMethod");}}
输出:
Sub's interestingMethod
public class SuperTest {public static void main(String[] args) {new Sub().exampleMethod();}}class Super {private void interestingMethod() {System.out.println("Super's interestingMethod");}void exampleMethod() {interestingMethod();}}class Sub extends Super {void interestingMethod() {System.out.println("Sub's interestingMethod");}}
输出:
Super‘s interestingMethod

代码二的Super类javap输出
Compiled from "SuperTest.java" 
class Super { 
Super(); 
Code: 
0: aload_0 
1: invokespecial #1 // Method java/lang/Object."<init>": 
()V 
4: return 

void exampleMethod(); 
Code: 
0: aload_0 
1: ldc #5 // String aa 
3: invokespecial #6 // Method interestingMethod:(Ljava/l 
ang/String;)I 
6: pop 
7: return 
}

 

代码一的Super类javap输出 写道
Compiled from "SuperTest.java" 
class Super { 
Super(); 
Code: 
0: aload_0 
1: invokespecial #1 // Method java/lang/Object."<init>": 
()V 
4: return 

int interestingMethod(java.lang.String); 
Code: 
0: getstatic #2 // Field java/lang/System.out:Ljava/ 
io/PrintStream; 
3: ldc #3 // String Super's interestingMethod 
5: invokevirtual #4 // Method java/io/PrintStream.printl 
n:(Ljava/lang/String;)V 
8: iconst_1 
9: ireturn 

void exampleMethod(); 
Code: 
0: aload_0 
1: ldc #5 // String aa 
3: invokevirtual #6 // Method interestingMethod:(Ljava/l 
ang/String;)I 
6: pop 
7: return 
}

 

    两边的不同我通过加粗来说明了.,在Super类的exampleMethod方法中调用interestingMethod方法的指令是不同的,代码二采用的是invokespecial 而代码一采用的是invokevirtual .

 

写道
· invokespecial - super方法调用、private方法调用与构造器调用 
· invokevirtual - 用于调用一般实例方法(包括声明为final但不为private的实例方法) 

    

其中

   

写道
invokespecial调用的目标必然是可以静态绑定的,因为它们都无法参与子类型多态;invokevirtual的则一般需要做运行时绑定
invokespecial调用的目标必然是可以静态绑定的,因为它们都无法参与子类型多态;invokevirtual的则一般需要做运行时绑定
所以说,有private 修饰的方法,是可以在编译器就找到调用版本的。invokerspecial调用的目标为interestingMethod,所以在编译器就能确定,一定是静态绑定。

其他的例子,
public class Dispatch{       static class QQ{};       static class Q1 extends QQ{};       static class Q2 extends QQ{};              public static class Father       {       public void hardChoice(QQ arg)       {       System.out.println("father choose qq");       }       public void hardChoice(Q1 arg)       {       System.out.println("father choose q1");       }       public void hardChoice(Q2 arg)       {       System.out.println("father choose q2");       }       }       public static class Son extends Father       {       public void hardChoice(QQ arg)       {       System.out.println("son choose qq");       }       public void hardChoice(Q1 arg)       {       System.out.println("son choose q1");       }       public void hardChoice(Q2 arg)       {       System.out.println("son choose q2");       }       }       public static void main(String[] args){       /*        * 两个宗量:方法的接收者和参数,我的理解是,参数是静态的,接收者是动态的,即直等到运行时才能确定接收者的实际类型        */Father father=new Father();QQ q =new Q1();QQ q1=new Q2();father.hardChoice(q);//相当于father的重载,静态绑定,输出father choose qqFather son=new Son();son.hardChoice(q);//动态分配是单分派,参数已经在静态期间确定了,实际类型运行时确定,那么为什么不在运行时确定q的实际类型呢?所以,我认为,
                                  //动态单分派,指的是接收者的单分派。}}

补充---------------------------------------》

关于静态分派和动态分派的理解,深入理解JVM书中所说:只要能被invokerstatic和invokerspecial指令调用的方法,都可以在解析阶段确定唯一的调用版本,就是说,只要在解析阶段能确定到底谁是要执行的目标函数时,就进行静态绑定了,那么我们在看上面的例子:
new sub()在编译期还不知道实际类型,只知道为Super类型,所以去找Super的exampleMethod方法




父类的exampleMethod方法 有 invokerspecial 指令,去找 interestingMethod,找到了,进行静态绑定


如果private变为public ,从Super类找的时候,exampleMethod 并不能找到调用目标,所以不能绑定,只有在运行时进行动态绑定。

0 0