java float调用方法 百度经验_JVM(六)如何执行方法调用

重写和重载

重载指的是方法名相同而参数类型不相同的方法之间的关系,重写指的是方法名相同而且参数列表也相同的方法之间的关系 。

public class OneOverride {

//=========================

// 这两个方法构成重载

public void show(){

}

public void show(String str) {

}

//===============================

}

/**

* 重写父类方法

*/

public class OneOverriderChilden extends OneOverride{

public void show(String str) {

}

}

java 虚拟机识别方法的关键在于类名,方法名以及方法描述符(method descriptor),方法描述符,它是由方法的参数类型以及返回类型所构成。

方法调用

Java中的方法调用分为两大类:

1、解析调用(Resolution): 在类加载的解析阶段,会把其中的一部分符号引用转化为直接引用。 前提是:方法在程序运行之前,就有一个可确定的调用版本,且该版本在运行期不可变。即“编译期可知,运行期不变”,符合这个要求的主要包括静态方法和私有方法两大类,前者与类型直接关联,后者外部无法调用,因此无法通过继承重写。

2、分派调用(Dispatch):又分为 “静态分派” “动态分派” “多分派” “单分派”。在运行期间才能确定调用方法的版本。

解析调用

jvm 字节码调用指令

jvm 提供了5条调用方法的字节码指令,分别是 :

invokestatic: 调用静态方法

invokespecial: 调用实例构造器方法、私有方法和父类方法

invokevirtual:调用所有的虚方法

invokeinterface:调用接口方法,会在运行时确定一个实现此接口的对象

invokedynamic: 先在运行时动态解析出调用点限定符所引用的方法,然后再执行

其中 invokestatic 和 invokespecial 在类加载阶段会把方法的符号引用解析成直接引用(内存地址入口),这类方法也称为非虚方法。

注意的是: final方法虽然是用invokevirtual来调用的,但是因为它无法被覆盖,是唯一的,不需动态解析的,所以它也是非虚方法。

来看个例子

public class StaticResolution {

public static void sayHello(){

System.out.println("hello world");

}

public static void main(String[] args) {

StaticResolution.sayHello();

}

}

这里调用了静态方法,那么使用 javap -v XX应该会使用 invokestatic 。

[root@iZm5e7bivgszquxjh18i39Z jvm测试]# javap -v StaticResolution

Classfile /home/jvm测试/StaticResolution.class

Last modified Mar 4, 2020; size 504 bytes

MD5 checksum f2bbab54fb03714e2332b782be397bfb

Compiled from "StaticResolution.java"

public class StaticResolution

minor version: 0

major version: 52

flags: ACC_PUBLIC, ACC_SUPER

Constant pool:

#1 = Methodref #7.#17 // java/lang/Object."":()V

#2 = Fieldref #18.#19 // java/lang/System.out:Ljava/io/PrintStream;

#3 = String #20 // hello world

#4 = Methodref #21.#22 // java/io/PrintStream.println:(Ljava/lang/String;)V

#5 = Methodref #6.#23 // StaticResolution.sayHello:()V

#6 = Class #24 // StaticResolution

#7 = Class #25 // java/lang/Object

#8 = Utf8

#9 = Utf8 ()V

#10 = Utf8 Code

#11 = Utf8 LineNumberTable

#12 = Utf8 sayHello

#13 = Utf8 main

#14 = Utf8 ([Ljava/lang/String;)V

#15 = Utf8 SourceFile

#16 = Utf8 StaticResolution.java

#17 = NameAndType #8:#9 // "":()V

#18 = Class #26 // java/lang/System

#19 = NameAndType #27:#28 // out:Ljava/io/PrintStream;

#20 = Utf8 hello world

#21 = Class #29 // java/io/PrintStream

#22 = NameAndType #30:#31 // println:(Ljava/lang/String;)V

#23 = NameAndType #12:#9 // sayHello:()V

#24 = Utf8 StaticResolution

#25 = Utf8 java/lang/Object

#26 = Utf8 java/lang/System

#27 = Utf8 out

#28 = Utf8 Ljava/io/PrintStream;

#29 = Utf8 java/io/PrintStream

#30 = Utf8 println

#31 = Utf8 (Ljava/lang/String;)V

{

public StaticResolution();

descriptor: ()V

flags: ACC_PUBLIC

Code:

stack=1, locals=1, args_size=1

0: aload_0

1: invokespecial #1 // Method java/lang/Object."":()V

4: return

LineNumberTable:

line 1: 0

public static void sayHello();

descriptor: ()V

flags: ACC_PUBLIC, ACC_STATIC

Code:

stack=2, locals=0, args_size=0

0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;

3: ldc #3 // String hello world

5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V

8: return

LineNumberTable:

line 4: 0

line 5: 8

public static void main(java.lang.String[]);

descriptor: ([Ljava/lang/String;)V

flags: ACC_PUBLIC, ACC_STATIC

Code:

stack=0, locals=1, args_size=1

0: invokestatic #5 // Method sayHello:()V

3: return

LineNumberTable:

line 8: 0

line 9: 3

}

SourceFile: "StaticResolution.java"

静态分派

在讲静态分派之前我们需要知道静态类型和动态类型,例如有以下程序 :

public class StaticDispatch {

static abstract class Human{}

static class Man extends Human{}

static class Woman extends Human{}

public void sayHello(Human guy){

System.out.println("Hello human");

}

public void sayHello(Man guy){

System.out.println("Hello man");

}

public void sayHello(Woman guy){

System.out.println("Hello woman");

}

public static void main(String[] args) {

Human man = new Man();

Human woman = new Woman();

StaticDispatch sr = new StaticDispatch();

sr.sayHello(man);

sr.sayHello(woman);

}

}

输出 :

Hello human

Hello human

上面的

Human man = new Man();

这里 “Human”是 man变量的 静态类型 (Static Type) 或者叫 外观类型(Apparent Type)而后面的 “Man” 则是 man 变量的 实际类型(Actual Type)。静态类型都实际类型在程序中都可以发生变化,** 区别在于静态类型的变化仅仅是在使用时发生,而其本身的静态类型并不发生改变。**

什么意思呢?就是 man 这个对象在被传作参数还是调用方法的时候,我们依然为会认为它是“Human”只有使用的时候它才是“Man”。

重载与静态分配

有三个关键点需要知道 :

静态类型在编译期可知,而动态类型只有实际运行时能够获知。

虚拟机是通过参数静态类型作为重载的判定依据

静态分派发生在编译阶段

但是重载有时候也会选择困难--我应该选择哪个重载方法,例如 :

public class Overload {

public static void sayHello(Object obj){

System.out.println("Hello object");

}

public static void sayHello(int arg){

System.out.println("Hello int");

}

public static void sayHello(long arg){

System.out.println("Hello long");

}

public static void sayHello(Character arg){

System.out.println("Hello character");

}

public static void sayHello(char ...arg){

System.out.println("Hello char ...");

}

public static void sayHello(Serializable arg){

System.out.println("Hello Serializable ");

}

public static void main(String[] args) {

sayHello('a');

}

}

输出 :

Hello int

重载的规则:

自身类型匹配

是否是基本类型,是,考虑自动装拆箱

形参的继承关系与重载方法是否匹配

变长参数匹配

另外以下也是静态分配 :

public class ResolutionAndDispatch{

static void sayHello(int arg){

System.out.println("Hello int");

}

static void sayHello(char arg){

System.out.println("Hello char");

}

public static void main(String[] args){

ResolutionAndDispatch.sayHello('a’);

}

}

分派调用

分派调用揭示了OOP多态性的一些最基本的体现。“重载”和“重写”,就是其中之一。

如下例子 :

public class DynamicDispatch {

static abstract class Human{

protected abstract void sayHello();

}

static class Man extends Human{

@Override

protected void sayHello() {

System.out.println("man say hello");

}

}

static class Woman extends Human{

@Override

protected void sayHello() {

System.out.println("woman say hello");

}

}

public static void main(String[] args) {

Human man = new Man();

Human woman = new Woman();

man.sayHello();

woman.sayHello();

man = new Woman();

man.sayHello();

}

}

可以看到子类重写了父类的方法。

[root@iZm5e7bivgszquxjh18i39Z jvm测试]# javap -v DynamicDispatch

Classfile /home/jvm测试/DynamicDispatch.class

Last modified Mar 4, 2020; size 514 bytes

MD5 checksum 7c19cd382f0b914eac869cb42608314f

Compiled from "DynamicDispatch.java"

public class DynamicDispatch

minor version: 0

major version: 52

flags: ACC_PUBLIC, ACC_SUPER

Constant pool:

#1 = Methodref #8.#22 // java/lang/Object."":()V

#2 = Class #23 // DynamicDispatch$Man

#3 = Methodref #2.#22 // DynamicDispatch$Man."":()V

#4 = Class #24 // DynamicDispatch$Woman

#5 = Methodref #4.#22 // DynamicDispatch$Woman."":()V

#6 = Methodref #12.#25 // DynamicDispatch$Human.sayHello:()V

#7 = Class #26 // DynamicDispatch

#8 = Class #27 // java/lang/Object

#9 = Utf8 Woman

#10 = Utf8 InnerClasses

#11 = Utf8 Man

#12 = Class #28 // DynamicDispatch$Human

#13 = Utf8 Human

#14 = Utf8

#15 = Utf8 ()V

#16 = Utf8 Code

#17 = Utf8 LineNumberTable

#18 = Utf8 main

#19 = Utf8 ([Ljava/lang/String;)V

#20 = Utf8 SourceFile

#21 = Utf8 DynamicDispatch.java

#22 = NameAndType #14:#15 // "":()V

#23 = Utf8 DynamicDispatch$Man

#24 = Utf8 DynamicDispatch$Woman

#25 = NameAndType #29:#15 // sayHello:()V

#26 = Utf8 DynamicDispatch

#27 = Utf8 java/lang/Object

#28 = Utf8 DynamicDispatch$Human

#29 = Utf8 sayHello

{

public DynamicDispatch();

descriptor: ()V

flags: ACC_PUBLIC

Code:

stack=1, locals=1, args_size=1

0: aload_0

1: invokespecial #1 // Method java/lang/Object."":()V

4: return

LineNumberTable:

line 1: 0

public static void main(java.lang.String[]);

descriptor: ([Ljava/lang/String;)V

flags: ACC_PUBLIC, ACC_STATIC

Code:

stack=2, locals=3, args_size=1

0: new #2 // class DynamicDispatch$Man

3: dup

4: invokespecial #3 // Method DynamicDispatch$Man."":()V

7: astore_1

8: new #4 // class DynamicDispatch$Woman

11: dup

12: invokespecial #5 // Method DynamicDispatch$Woman."":()V

15: astore_2

16: aload_1

17: invokevirtual #6 // Method DynamicDispatch$Human.sayHello:()V

20: aload_2

21: invokevirtual #6 // Method DynamicDispatch$Human.sayHello:()V

24: new #4 // class DynamicDispatch$Woman

27: dup

28: invokespecial #5 // Method DynamicDispatch$Woman."":()V

31: astore_1

32: aload_1

33: invokevirtual #6 // Method DynamicDispatch$Human.sayHello:()V

36: return

LineNumberTable:

line 20: 0

line 21: 8

line 22: 16

line 23: 20

line 24: 24

line 25: 32

line 26: 36

}

SourceFile: "DynamicDispatch.java"

InnerClasses:

static #9= #4 of #7; //Woman=class DynamicDispatch$Woman of class DynamicDispatch

static #11= #2 of #7; //Man=class DynamicDispatch$Man of class DynamicDispatch

static abstract #13= #12 of #7; //Human=class DynamicDispatch$Human of class DynamicDispatch

0~15 在做准备动作 我们看到调用了两次 invokespecial 是调用了实例构造器 构造了man 和woman两个实例,并且把他们的引用放在1、2个局部变量表Slot中接下来的16~21,16和20两句aload_1和aload_2 把创建的对象的引用压到栈顶,这两个对象是将要执行的方法sayHello()的执行者,称作接受者(Receiver) 17和21两句的方法调用指令 和参数 都是一样的,但是最终执行的目标方法不同,原因就是invokevirtual指令的多态查找

虚方法调用

java 里所有非私有实例方法调用都会被编译成 invokevirtual 指令,而接口方法调用都会被编译成 invokeinterface 指令,这均属于java虚拟机中的需方法调用。

**java 虚拟机采取了一种空间换时间的策略来实现动态绑定。**它为每个类生成一个方法表,用以快速定位目标方法。

方法表

方法表满足两个特性 :

子类表中包含父类表中的所有方法

子类方法在方法表中的索引值,与它所重写的父类方法的索引值相同

在执行过程中,java虚拟机将获取调用者的实际类型,并在该实际类型的虚方法表中,根据索引值获得目标方法。这个过程便是动态绑定。

思考一下假如我们如果不使用方法表,我们就需要先去收集然后再查找目标方法了,但是即使使用了方法表还有没优化的空间呢?即时编译(JIT)还拥有另外两种性能更好的优化手段 : 内联缓存 和方法内联

内联缓存

它能够缓存虚方法中调用者的动态类型,以及该类型对应的目标方法。 在之后的执行过程中,如果碰到已缓存的类型,直接在缓存中找到对应的目标方法,没有找到,那么就会去方法表中寻找。

方法内联

后续讲解

补充

查看汇编后的java class

javap -v xxx

参考资料

https://tobiaslee.top/2017/02/14/Override-and-Overload/

《深入JVM》课程

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值