33

Java 的多态在 JVM 里原来是这样的

 3 years ago
source link: http://mp.weixin.qq.com/s?__biz=MzI3MTEwODc5Ng%3D%3D&%3Bmid=2650861022&%3Bidx=1&%3Bsn=c0d77ad9acff85342ea7bb9da7ca190e
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

JrMbyqb.jpg!web

多态

面向对象的编程语言里,「 多态 」是一个至关重要的概念。我们常说,面向对象的本质,是 方法与数据的绑定 。那对于一个拥有继承关系的类之间,方法的绑定,是终是子类「重写」父类的方法,通过父类的引用指向子类的对象,实现运行时的多态。

说起来比较绕,我们先以仅次于Hello World 著名的 「动物 - 狗」代码来说明多态,然后再来分析在 JVM 层面,多态是怎样实现的。

package com.example.demo;

public class Demo {

public static void main(String[] args) {

Animal a = new Animal();

a.say();

Dog d = new Dog();

d.say();

Animal ad = new Dog();

ad.say();

}

}

class Animal {

public void say() {

System.out.println("Animal say");

}

public void play() {

System.out.println("play...");

}

}

class Dog extends Animal {

public void say() {

System.out.println("Dog say");

}

}

输出的内容对于习惯了面向对象的 Java 开发者来说都比较熟悉

Animal say

Dog say

Dog say

那虚拟机是怎样知道到底要调用 Animal 的 say 还是 Dog 的say呢?

咱们从字节码的层面来看一下。

0 new #2 <com/example/demo/Animal>

3 dup

4 invokespecial #3 <com/example/demo/Animal.<init>>

7 astore_1

8 aload_1

9 invokevirtual #4 <com/example/demo/Animal.say>

12 new #5 <com/example/demo/Dog>

15 dup

16 invokespecial #6 <com/example/demo/Dog.<init>>

19 astore_2

20 aload_2

21 invokevirtual #7 <com/example/demo/Dog.say>

24 new #5 <com/example/demo/Dog>

27 dup

28 invokespecial #6 <com/example/demo/Dog.<init>>

31 astore_3

32 aload_3

33 invokevirtual #4 <com/example/demo/Animal.say>

36 return

你发现没有,在字节码的第9行,和第33行,分别对应到 d.say() 和 ad.say() ,但指令内容其实是一样的。这就神奇了。

在这两个方法执行前,第8行和第32行,会有一个aload的操作,前面的文章里有介绍过( 看看 JVM 是怎样消化字节码指令的 ~~ ),是把这两个对象的引用 压到栈顶,给后面的操作用。这两个对象,一般也被称为方法的接收者(Receiver),如果熟悉 Golang等语言的朋友,对这个概念也不陌生。

从9行和第33行看,无论是方法调用的字节码指令还是参数,都指向了常量池的第4项。都是一样的,但最终结果并不相同。这里的重点在于 invokevirtual 这个指令的多态指行查找过程,即根据对象的  vtable   在运行时定位方法。

啥是 vtable?

前面的内容提到指令执行时从栈顶获取当前方法的「 接收者 」,通过invokerirtual 来执行这个接者者对应的方法。 注意这里的 virtual,和C++的虚方法类似。这个咱们不提,只说Java 的。

对象都有一个自己的「方法表」,这个表里除了自己的方法,还有从父类继承来的方法,甚至重写的父类的方法。所以,对应于 重写重载 ,体现在方法表里也有所区别。每个子类继承父类的时候,都将直接复制一份父类的方法表,而对于父类方法的重写,会直接更新方法表里相同顺序的这个方法。

而重载,本质上由于签名及参数的区别,是一个新的方法,在方法表里会是新增一个元素。

这里的这个 方法表 ,就是咱们说的 vtable(Virtual Method Table),表里的每个方法,对应的是它的实际执行入口地址。如果没有重写,那父类和子类的地址是一样的,都指向父类的实现。

果子类重写之后,子类方法表里的这个方法的地址就指向了自己实现的版本。

而我们上面字节码处观察到的,两个 invokevirtual 对应的常量池索引序号是一样的,这样实现对于变换实现类型时,查找方法表只需要换个对象,索引依旧相同。

观察

理解了方法表大概的原理,我们来解剖下,上咱们的JVM「显微镜」( Java虚拟机的显微镜 Serviceability Agent )。

为了便于 Attach 到 Java 进程,可以在代码里加下 latch 进行 awiat 阻塞,启动 SA 就能观察了。

eEruaiM.png!web

选择 ClassBrowser

MNbA3uj.png!web

在 Class列表里就能找到咱们上面创建的对象。@ 符号后面是这个对象对应的内存地址。复制上Dog的地址,再从菜单里选择Inspector,

你看 _vtable_len: 7

RnmmIzi.png!web

这是告诉我们 vtable 长度是7,里面有7个方法。

实际上咱们在这个类里只重写了父类 Animal 的 say方法,其它的是从 Animal 继承来的 play方法,以及超类 Object 里的 5个方法,大概这个样子

ARFzuyB.png!web

JVM 在首次加载类的时候,会解析类内包含的方法,方法解析之后就会计算当前类 vtable的大小。

可能你会问,Object 类内不止5个方法,为什么只算5个呢?而且我们新增其它static、 final 这一类的方法呢?

这里 vtable 只计算非static final 的,全部计算完就得出了vtable_len这个值。

每个 Java 的 Class 在 JVM 内部都会有一个自己的instanceKlass, vtable就分配在这个的最后。

整个instanceKlass的大小,在64位系统里大小是 0x1b8 ,记住它,后面用的着。  所以咱们上面看到了Dog 类的内存地址,继续找就能看到他其它方法对应的内存地址。

在Windows -> console 里执行这个:

mem 0x7C0060DD0 7

这个值怎么来的呢?是从对象的内存地址开始,加上 instanceKlass的大小。

0x7C0060DD0   =  0x00000007c0060c18 + 0x1b8

由于我们有7个方法,所以顺序查找7个地址。

所以你应该也发现了,Java 里对应这种重写的方法,是在类加载的时候,才能知道具体对应的是哪个方法,因此也被称为动态绑定或者迟绑定。

总结起来,这里的 vtable,相当于你的工具清单,有什么能力都做了罗列,像钢铁侠的各项技能,每个功能指向具体的超能力,在我们代码里可以把它理解成一个数组,数组的每个元素指向一个方法地址。

感兴趣的话,你加个static 的方法自己找找,看看在不在这里面呢?毕竟static方法执行不是有 invokestatic 指令嘛。

看点别的

怎样计算一个Java对象大小?这儿有几种方法~

听保洁的老大爷讲Java的垃圾回收

俯瞰Java 虚拟机

在 Java 里创建一个对象要经过哪些步骤?

看看 JVM 是怎样消化字节码指令的 ~~

你写下的try-catch-finally,在JVM看来不过是...

理解了1+2的过程,就理解了Java虚拟机

问题诊断神器BTrace

Java七武器系列霸王枪 -- 线程状态分析 jstack

Tomcat的异步Servlet实现原理

监控诊断工具 Arthas 源码原理分析

Java虚拟机的显微镜 Serviceability Agent

怎样回答技术面试题?

怎样了解你的线程在干嘛?

更多常见问题,请关注公众号,在菜单「 常见问题 」中查看 ,也欢迎加我微信,一起交流。 

QjaIbeu.png!web

VzIjauI.jpg!web

源码|实战|成长|职场

这里是「 Tomcat那些事儿

请留下你的足迹

我们一起「终身成长」

y6n6Bzn.jpg!web

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK