您的位置:首页 > 其它

方法区

2015-11-30 13:39 190 查看

方法调用

方法调用不等于方法执行,其唯一的任务就是确定调用哪一个具体方法,暂时还不涉及方法内部的具体运行过程。在程序运行时,进行方法调用时最普遍,最频繁的操作。

解析

所有方法调用中的目标方法在Class文件里面都是一个常量池中的符号引用。在类加载的解析阶段,一部分符号引用会被转化为直接引用,这种解析成立的前提是:方法在程序真正运行之前就有一个可确定的调用版本,且这个方法的调用版本在运行时是不可改变的。符合这个条件的有静态方法和私有方法两大类。

JVM提供了4条方法调用的字节码指令:

invokestatic:调用静态方法
invokespecial:调用实例构造器<init>方法,私有方法和父类方法
invokevirtual:调用所有的虚方法
invokeinterface:调用接口方法,会在运行时再确定一个实现此接口的对象。


只要能被invokestatic和invokespecial调用的方法,都可以在解析阶段进行转化。

除此以外(除静态方法,实例构造器,私有方法,父类方法以外)其他方法称为虚方法。

JAVA非虚方法除了invokestatic和invokespecial以外,还有一种就是final修饰的方法,因为该方法无法被覆盖,这种被final修饰的方法是用invokevirtual指令调用的。在java语言规范中明确说明了final方法是一种非虚方法。

分派-多态的揭示

  众所周知,面向对象的三个特点是:”继承,封装,多态”,其中多态又包括重写和重载,分派揭示的就是重载和重写:

1.静态分派-重载的揭露

  Human human = new Woman();

这句中,Human被称为静态类型(也叫外观类型),Woman称为实际类型。

虚拟机(编译器)重载时通过参数的静态类型作为判断依据。所有依赖静态类型来定位方法执行版本的分派动作,都称为静态分派。静态分派的最典型应用就是方法重载。在重载的情况下,很多时候重载版本并不是唯一的,而是寻找一个最合适的版本。比如存在多个重载方法的情况中,调用的目标顺序是一定的。

找到合适的版本顺序是在:

char->int->long->float->double(在确定转型安全的情况)->装箱(装箱不会自动转型)->Serializable->Object->可见变长参数(不会自动转型)

2.动态分派——重写的揭露

  相对于前面的重载中,引用类型对于具体调用哪个方法起决定性作用,在重写中,引用指向的对象的具体类型决定了调用的具体目标方法。

//其余代码省略,这里man和woman都重写了human的sayHello方法,在main方法调用
Human man = new Man();
Human woman = new Woman();
man.sayHello();
woman.sayHello();
man = new Woman();
man.sayHello();


   main方法的字节码:

0: new #16;
3; dup
4: invokespecial #18;  //调用<init>
7; astore_1
8: new #19;
11:dup
12:invokespecial #21;  //调用<init>
15:astore_2
16:aload_1
17:invokevirtual #22;
20:aload_2
21:invokevirtual #22;
24:new #19;
27:dup
28:invokespecial #21;
31:astore_1
32:aload_1
33:invokevirtual #22
36:return


0-15行是准备工作,用于生成对象,初始化对象,并将两个实例存放在第一和第二个局部变量表slot中。

16和20行分别将刚创建的两个对象引用压到栈顶,这两个对象是将要执行的sayHello()方法的所有者,成为接收者。

17和21行是方法调用指令,可见指令和参数都是一样的,都是invokevirtual常量池中第22项的常量—Human.sayHello()的符号引用,而结果是这两次调用的结果不同,原因是invokevirtual指令的运行时解析过程:

1.找到操作数栈顶的第一个元素所指向的对象的实际类型,记作C。

2.如果在类型C中找到与常量中的描述符和简单名称都相符的方法,则进行访问权限校验,通过则返回方法直接引用,不通过抛出java.lang.IllegalAccessError

3.否则,按照继承关系从下往上一次对C的各个父类进行第二步的搜索和验证。

4.没找到合适方法,抛出java.lang.AbstractMethodError异常

由于第一步是解析成对象的实际类型,因此两次调用的结果不一样。

这个顺序实际是:

找到实际类型—在该类型中搜索–在该类型的继承结构中自下而上搜索—没找到抛出异常

单,多分派

  根据分派基于多少种宗量,可以将分派划分为单分派和多分派两种。单分派是根据一个宗量对目标方法进行选择,多分派是根据多于一个宗量对目标方法进行选择。

在重载过程中,要是有多种不同方法(宗量)的重载,根据符号引用判断,就称为多静态分派。

重写过程中是根据虚拟机选择的因素只有接受者的实际类型是谁。只有一个宗量,所以属于单动态分派。

方法表

   由于动态分派非常繁琐以及虚拟机实际实现中基于性能考虑,通常都会对动态分派的实现做优化。最通常的优化方法就是在类的方法区中建一个虚方法表(Virtual Method Talbe,vtable),于此对应,invokeinterface执行时也用到接口方法表(Interface Method Table,itable)。

                            



  虚方法表中存放着各个方法的实际入口地址。如果某个方法在子类中没有被重写,那么子类的虚方法表中的地址入口地址与父类相同方法的地址入口地址一致,都指向父类的实现入口。如果子类重写了这个方法,那么子类虚方法表中地址将会被替换成指向子类实现版本的入口地址。上图中Son重写了来自Father的全部方法,因此Son方法表中这些方法的实际入口地址都指向了Son类型数据的方法。Son和Father都没有重写Object中的方法,所以方法表中的实际入口地址都指向了Object数据类型。

  为了程序实现上的方便,具有相同签名的方法,在父类、子类的虚方法表中都应当具有一样的索引序号,这样当类型变换时,仅需要变更查找的方法表,就可以从不同的虚方法表中按索引转换出所需的入口地址。

  方法表一般在类加载的连接阶段进行初始化,准备了类的变量初始值后,虚拟机会把该类的方法表也初始化完毕。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: