JVM系列之函数调用入门

刚开始尝试深入写JVM相关内容,语言尽量通俗,有不懂的地方欢迎留言一起探讨~

写在最前面

James Gosling,java创始人,被称之为“java之父”,从write once, run anywhere!可以看出,James其实是想开发一款可以在任何平台运行的语言。在当时,其实很多编程语言都具备了这种能力,比如c语言,估计最难的一点就是怎么样在开发层面实现平台的无关性了。

那么c语言又是怎么实现兼容的呢?
c语言在实现兼容的方式可谓是简单粗暴啊,不同的平台就用不同的编译器嘛,直接将c语言编译成底层平台可以运行的机器指令。虽然这种简单粗暴的方式还算是很好的解决了兼容性问题,但是开发者就惨了,不同的系统底层调用的API是不一样的,开发者在做代码开发的时候不光要关注功能相关逻辑,还必须要关注底层的API。

James可不想这样累死开发者,对于这个问题,他想了一个解决办法,我们为何不搞一个专门的模块帮开发者做这些呢,就这样,虚拟机(JVM)和字节码规范就应运而生了,程序会被编译成字节码,由虚拟机解释执行字节码。

注:其实综上所述,一款语言要做底层系统的兼容性大致分为两种方案:

  1. 通过编译器实现兼容:
    比如c/c++,编译器赋予了它们可以在不同平台运行的能力。针对不同的系统,开发特定的编译器,编译器可以把程序翻译成平台可以识别的机器指令,从而实现兼容性。

  2. 通过中间语言实现兼容:
    比如java,编译后,生成中间语言,虚拟机解释运行该中间指令,无论程序最重运行在哪个平台,编译生成的中间语言指令都是相同的(.class文件),至于和平台的兼容性,由虚拟机来完成。

划水半篇文章,顺便介绍下java设计的一些背景知识后,接下来我们就进入正题,看看JVM底层到底是怎么实现方法调用的。

方法调用

为什么要先介绍方法调用?
其实我也不想先介绍方法调用,但是它是基础啊,是整个Java执行引擎可以正常run起来的重点。说白了,JVM作为一款虚拟机,它肯定是需要涉及到计算机的3大核心功能的:

  1. 方法调用:学过计算机的都知道,方法是作为程序组成的一个最基本的单元,而对于Java来说,原子指令其实就是字节码,Java方法也就是对字节码的封装,Java程序要想愉快的run起来,那JVM必须要支持对Java方法的调用;

  2. 取出指令:方法是对原子指令的封装,那最终在CPU上执行其实也就是指令逐条取出并执行,Java的方法执行也是一样的流程,这个时候就需要JVM配合了,JVM需要模拟CPU,逐条取出字节码指令并执行;

  3. 运算:CPU取出指令就可以根据指令做相应的逻辑运算了,当然,JVM也需要具备字节码的运算能力。

提起方法调用,有了解过JVM的一定多少有听说过call_stub()函数。对,就是它,该函数在整个JVM中有着非常重要的作用,接下来我们就来看看JVM是如何实现的。

call_stub函数定义

CallStub函数定义

从源码可以看出,call_stub函数调用了一个宏CAST_TO_FN_PTR,我们来看下这个宏干了些什么:

#define CAST_TO_FN_PTR(func_type, value) ((func_type)(castable_address(value)))

把call_stub函数的宏替换下:


CallStub函数替换宏定义

函数定义已经清晰了,那我们来看看CallStub这个JVM自定义的类型:


CallStub定义

看到这里小伙伴们肯定不淡定了,这个call_stub函数不就是c语言里典型的函数指针么。指向的函数返回值类型是void,并且有8个入参,接下来我们就以call_stub函数的调用来依次做相关介绍。

注:c语言中相近的定义还有指针函数,但是它俩是完全不同的,一个重点是函数,一个重点是指针变量,具体的差别有兴趣的小伙伴请自行百度~~~

call_stub函数调用

JVM在javaCalls::call_helper()调用了该函数,我们来看看是怎么调用的:

注:javaCalls::call_helper()在javaCalls.cpp中实现

CallStub调用

从源码可以看出,JVM隐式的调用了函数指针,我们来改一下这段源码,就一目了然了:


修改后的CallStub调用

由于JVM在申明CallStub的时候就定义了该函数指针需要8个入参,所以JVM最终在调用的时候也按照约定,传入了8个类型相同的参数。我们再结合call_stub()方法的定义来具体还原call_stub()方法的逻辑。

call_stub函数逻辑还原

上文已经简单给出了call_stub函数的定义,从call_stub函数定义可以知道,它其实是调用了方法castable_address方法,并将其转换成CallStub类型(函数指针),JVM通过调用其函数指针完成函数的调用,说白了,call_stub的目的就为了让函数指针指向某个函数(内存地址)~

castable_address实现

注:castable_address方法定义在globalDefinitions.hpp中

castable_address实现

从源码可以看出,castable_address方法将入参x转换成了address_word类型:

注:address_word也是一个自定义类型,同样定义在globalDefinitions.hpp中

address_word定义

从address_word定义可以看出,它的类型其实是uintptr_t,从源码注释可以看出来,uintptr_t其实是一个unsigned integer(无符号整数),由于这种类型跟平台相关,所以JVM在3个地方定义了改类型:

  • globalDefinitions_gcc.hpp:Linux操作系统

  • globalDefinitions_sparcWorks.hpp:MacOs操作系统

  • globalDefinitions_visCPP.hpp:Windows操作系统

我们以Linux平台为例,我们来看一下uintptr_t的定义:


uintptr_t定义

call_stub基本逻辑还原

到这里,call_stub函数可以继续替换成这样:


call_stub替换实现

从替换后的源码实现可以看出:

  • call_stub函数首先将_call_stub_entry转换成unsigned int类型;

  • 将转换后的unsigned int类型转换成CallStub类型。

不是说好的call_stub()会让CallStub函数指针指向某个函数么,怎么指向的啊?就只看到了把_call_stub_entry转换成了CallStub类型~~~对,你想的没错,_call_stub_entry就是待调用函数的内存地址,我们来看看_call_stub_entry相关声明和初始化吧。

_call_stub_entry

  • _call_stub_entry声明:

注:_call_stub_entry声明在subRoutines.hpp中

_call_stub_entry声明
  • _call_stub_entry初始化:
    在JVM初始化的时候,_call_stub_entry就会被初始化指向某一个内存地址,以Linux x86 64位系统为例:

注:_call_stub_entry初始化位于stubGenerator_x86_64.cpp中

_call_stub_entry初始化

从加框部分源码可以看出来,_call_stub_entry是通过方法generate_call_stub完成初始化的。

注:generate_call_stub方法中涉及到堆栈内存分配等操作,是JVM核心功能,下一篇文章会单独做详细分析介绍,在这里就不做深入介绍~

CallStub入参

在开始下一篇文章详细分析generate_call_stub()方法初始化_call_stub_entry之前,再做最后一个入门的基础知识介绍:CallStub的8个入参。
我们来回想上文中给出的JVM调用call_stub()方法源码,JVM在调用时一共传入了8个参数:

  1. link
    连接器,类型是JavaCallWrapper,我们来看一下该类型的定义,看看这个link到底想要链接谁~

    JavaCallWrapper定义

    从源码可以看出,JavaCallWrapper主要包含以下私有变量:

    • _thread:当前函数所在线程;

    • _handles:调用句柄;

    • _callee_method:调用者方法对象;

    • _receiver:被调用者;

    • _anchor:Java线程堆栈对象;

    • _result:方法返回值。

    通过这些私有变量可以看出,link主要连接了函数的调用者和被调用者~当然,函数在调用时,link指针也会被保存到当前方法的堆栈中。

  2. result_val_address
    函数返回值地址。

  3. result_type
    函数返回类型。

  4. method()
    当前方法在JVM中的表示对象。每一个方法在被加载的时候,JVM都会为其建一个模型,保存该方法所有的原始描述信息,主要包括:

    • 方法的名称,所属的类;

    • 方法的入参信息,包括入参类型,入参参数名,入参数量,顺序等;

    • 方法编译后的字节码信息,包括对应的字节码指令等;

    • 方法的注释信息;

    • 方法的继承信息;

    • 方法的返回信息

    method()参数的意义就是为了让JVM可以通过method()对象获取到Java方法编译后的字节码信息,JVM在拿到字节码后就可以解释执行了~

  5. entry_point
    JVM每次在调用Java函数时,必然会调用CallStub函数指针,当然咯,这个函数指针的值就是_call_stub_entry,JVM通过_call_stub_entry指向被调用函数地址,最终调用函数。在调用函数之前,必须要先经过entry_point,JVM实际是通过entry_point从method()对象上拿到Java方法对应的第一个字节码命令,这也是整个函数的调用入口。

  6. parameters()
    方法入参信息。JVM在调用函数之前,会通过该参数为函数分配堆栈,并将入参入栈。

  7. size_of_parameters()
    方法入参数量。

  8. CHECK
    当前线程对象。

到这里为止,函数调用一些入门的基础就介绍完了。下一章继续啃骨头,_call_stub_entry的初始化~

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,117评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,328评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,839评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,007评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,384评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,629评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,880评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,593评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,313评论 1 243
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,575评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,066评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,392评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,052评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,082评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,844评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,662评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,575评论 2 270

推荐阅读更多精彩内容