性能文章>从hotspot源码层面剖析Java的多态实现原理>

从hotspot源码层面剖析Java的多态实现原理原创

2252028

哈喽,我是子牙。十余年技术生涯,一路披荆斩棘从技术小白到技术总监到JVM专家到创业。技术栈如汇编、C语言、C++、Windows内核、Linux内核。特别喜欢研究虚拟机底层实现,对JVM有深入研究。分享的文章偏硬核,很硬的那种。

 

手撸过JVM、内存池、垃圾回收算法、synchronized、线程池、NIO…

 

 

本篇文章是接上篇文章【JVM的多态是如何实现的】写的,如果你还没看过,墙裂都建议你看一下。传送门

 

上篇文章我给出了这道面试题的及格分的回答及七八十分的回答,今天我就告诉大家如果想回答得接近满分,应该怎么回答。因为会设计到C++的虚表及C++的多态实现,如何这块你不理解或不熟,面试中建议别拿出来说,免得碰到懂C++给你来个连环call把你问懵了。

 

这边给大家补一个知识点。我在昨天的文章里说:当时设计OOP机制的时候,能够想到多态的人,真特么太牛叉了。我给大家讲一下我为什么这样说。或者说,OOP三大机制为什么就是封装、继承、多态。这么几十年了,没加一个、减一个或改一个。

 

由于多态需要通过动态绑定才能得以实现,而绑定通俗一点讲就是让不同的对象对同一个函数进行调用,或者反过来讲,就是让同一个函数与不同的对象绑定起来,所以多态得以实现的一个大前提就是,编程语言必须是面向对象的。同时,函数与对象相互绑定,意味着函数也属于对象的一部分,这便具备了封装的特性。因为有了封装,才有了对象。同时,一个函数能够绑定多个对象,意味着对各不同的对象具有相同的行为,这是继承的含义。

 

因此,面向对象的三大特性缺一不可。封装与继承其实是为了多态准备的,或者说,封装与继承成全了多态,多态让封装与继承的意义最大化。

01

C++是如何实现多态的

 

多态的实现,现在几乎所有的编程语言都是基于虚表实现的,英文vtable。这里我没有说全部,因为我也不是所有的语言都了解哈,不敢乱说,免得遭喷。^_^

 

C++的虚表在哪呢?在new创建的对象的头部。虚表里面存储的是什么呢?是虚函数。C++这块的知识我就不讲太多了,很多小伙伴不了解C++,讲多了没必要,作为一名Java程序员,了解到这个程度够了。

 

 

因为hotshot主要是用C++写的,讲了C++的虚表,这张图你应该就能看懂了。

 

 

不然总有小伙伴问我:Java的类对应的C++对象,为什么有C++级别的虚表啊。我没看到哪里有这样的代码啊。

 

搞清楚了虚表,再来了解虚表分发就容易多了。虚表分发,其实就是通过虚表内存地址拿到虚表记录,然后通过函数名+内含参数信息及返回值信息的签名去虚表中找。因为是从前往后找,所以如果子类重写了父类的方法,会调用子类的方法。

 

C++的虚表分发,我只是简单讲了下,讲多了大家没概念。JVM的虚表分发,我等下会讲得详细一些。很多现象,如果不了解它的底层,是不是百思不得其解。有那么多为什么?为什么?^_^

 

所以Java虽好,底层也很重要。顺便说下,虚表就是用数组实现的,没有有些小伙伴想得那么复杂。

02

JVM中的虚表

 

JVM的虚表跟C++的虚表还不太一样。不一样体现在哪呢?研究虚表研究三个东西:虚表在哪、虚表是用什么结构实现的、虚表分发机制是怎样的。JVM的虚表分发等下讲,JVM的虚表也是用数组实现的,那这个不一样就体现在虚表在哪?

 

Java的类,JVM中对应的C++对象是klass模型。Java的对象,JVM中对应的C++对象是oop模型。C++中的虚表在对象头中,而JVM的虚表在klass模型的头部,即Java类对象的头部。这点区别一定要记住,这样你才能理解Java对象的内存布局。

 

问个问题:我们随便定义的一个类,它有没有JVM虚表呢?其实是有的。那是哪些方法的内存地址呢?回答这个问题前先得搞明白:什么样的方法会存入虚表。只有public、protect类型的,且不被static、final修饰的方法才能被多态调用,才会进入虚表。因为Java中所有的类都是Object的子类,所以Object中满足这个条件的方法都会在每个类的虚表中。

 

又到了小伙伴不服气环节。么事,上证据。具体怎么查看我就不讲了,有点复杂。对hotspot没一定的功力讲了也没概念。

 

03

Java是如何实现虚表分发

 

有些小伙伴不理解:我只会Java干活都没问题呀,我为什么要学底层呢?那你想进大厂跟优秀的人成为同事吗?你想成为别人眼中的大佬吗?你希望在某个领域能有一定的名气吗……这些都需要实力来支撑。

 

有些小伙伴说:我手写一个JVM干什么呢?那我就用我手写的JVM来讲解这个知识点。这就是你有一个手写JVM的意义之一。

 

JVM实现虚表分发,对应的字节码指令有两个:invokevirtual、invokeinterface。上篇文章咱们深入讲解了invokeinterface,这篇文章咱们继续拿这个指令来讲这个知识点。我们来看看JVM是如何分发的。其实一看执行invokeinterface时的堆栈,你应该就能明白了。

 

 

虽然invokeinterface后面的操作数是接口方法信息。但是真正的对象会作为this传过来。所以在调用的时候,从操作数栈拿到真正的对象,然后通过对象头中的类型指针拿到TestDuotai对应的C++类对象,即klass模型。前面说了,虚表就在这个对象的头部。然后通过函数名+内含参数信息及返回值信息的签名去虚表中找。因为是从前往后找,所以如果子类重写了父类的方法,会调用子类的方法。这就是JVM虚表分发的底层原理。

 

这块有点难理解,需要的基础可能比较深。我不知道我这样写小伙伴们能不能看懂,怎么看都看不懂的小伙伴可以加我微信(jvm-ziya)来问我。

 

 

 

 

 

你好,我是子牙。十余年技术生涯,一路披荆斩棘从小白到技术总监到大厂中间件到创业。技术栈如汇编、C语言、C++、Windows内核、Linux内核及特别喜欢研究虚拟机底层实现,对JVM有深入研究。分享的文章偏硬核,很硬的那种。不考虑交个朋友吗?关注硬核子牙

 

点赞收藏
分类:标签:
子牙_公号硬核子牙

对编程语言的设计与实现有浓厚兴趣。聚焦Hotspot源码、Linux内核研究,硬核干货分享

请先登录,感受更多精彩内容
快去登录吧,你将获得
  • 浏览更多精彩评论
  • 和开发者讨论交流,共同进步

为你推荐

【全网首发】一次想不到的 Bootstrap 类加载器带来的 Native 内存泄露分析

【全网首发】一次想不到的 Bootstrap 类加载器带来的 Native 内存泄露分析

记一次线上RPC超时故障排查及后续GC调优思路

记一次线上RPC超时故障排查及后续GC调优思路

解读JVM级别本地缓存Caffeine青出于蓝的要诀 —— 缘何会更强、如何去上手

解读JVM级别本地缓存Caffeine青出于蓝的要诀 —— 缘何会更强、如何去上手

【全网首发】一次疑似 JVM Native 内存泄露的问题分析

【全网首发】一次疑似 JVM Native 内存泄露的问题分析

解读JVM级别本地缓存Caffeine青出于蓝的要诀2 —— 弄清楚Caffeine的同步、异步回源方式

解读JVM级别本地缓存Caffeine青出于蓝的要诀2 —— 弄清楚Caffeine的同步、异步回源方式

【全网首发】从源码角度分析一次诡异的类被加载问题

【全网首发】从源码角度分析一次诡异的类被加载问题

28
0