总结call_stub()中的几个重要参数

水深无声 2023-02-16 04:08 93阅读 0赞

接着上一篇日志,先回顾下Java主函数调用时必须执行的call_stub函数:

  1. static CallStub call_stub()
  2. {
  3. return (CallStub)(castable_address(_call_stub_entry);
  4. }

castable_address是一个函数,展开后返回的是address_word类型,其最终原型就是unsigned int,_call_stub_entry也是unsigned int类型,指向了某个内存地址。返回值最终会被类型转换为CallStub类型的函数指针,总的来说,call_stub()函数的调用逻辑就是,在JVM初始化时,便会让_call_stub_entry(unsigned int类型) 指向某一内存区域,接着类型转换成CallStub自定义的函数指针,最后返回,JVM将call_stub()的返回值函数指针作为函数调用。CallStub的结构上一篇日志有写,一个有八个参数,其中result_val_address返回值地址,result_type返回值类型和size_of_parameters入参数量,这些比较简单,而link连接器、Java方法对象method()、调用Java方法的入口函数entry_point以及方法参数集合parameters等对于JVM调用Java方法来说十分重要。

先来看看link类型的结构,连接器起到连接的作用,建立调用函数和被调用函数之间的连接,例如main()函数里调用了run()函数,则连接器为其建立连接,让它们之间可以完成传参,入参,返回等操作。

  1. class JavaCallWrapper: StackObj {
  2. friend class VMStruct;
  3. private:
  4. JavaThread* _thread;
  5. JNIHandleBlock* _handles;
  6. methodOop _callee_method;
  7. oop _receiver;
  8. JavaFrameAnchor _anchor;
  9. JavaValue* _result;
  10. };

JavaThread标识的显然就是当前Java方法线程,_handles表示的是调用句柄,句柄就是Java中的引用,这里不详细解释。_callee_method指的是调用者的方法对象,_receiver是被调用者,_anchor,线程堆栈,_result很容易知道就是方法的返回值。通过link建立连接后的调用者和被调用这,例如main()函数里调用run()函数,main()函数可以在自己的栈空间中把参数压栈,run()函数可以从main()函数的栈中读参入参。

method()方法对象

method()方法对象表示的是当前调用的Java方法在JVM内部建立的函数模型,这个模型包含了Java方法的全部信息,例如方法名、入参类型、入参数量、编译后的字节码指令,注解和返回信息等。JVM在调用call_stub()函数执行某一Java方法时,会通过method()对象得到Java方法编译后的字节码,得到字节码后JVM才能对其进行解释执行。看到这里你可能会想,仅仅为了得到字节码来解释运行,为什么还要建立整个Java方法的函数模型,存储这么多待执行方法的信息呢?直接要字节码不就够了吗?对的,不过大家还记得Java中提供的反射机制吗?反射对象可以获得一个Java类的信息,包括类中的方法信息,用的是Class类,随便找一个反射例子:

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2p1c3RpbnplbmdUTQ_size_16_color_FFFFFF_t_70

例子中有一个ReenterLock类,类中有run()方法和main()方法,在main()方法程序运行时,先实例化ReenterLock类的一个实例对象L1,然后通过Class.forName().getMethods()方法等,可以获得类中包含的方法,直接调用类实例对象的getClass()方法可以获得该类的一个“模板”,输出它的类名等信息:

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2p1c3RpbnplbmdUTQ_size_16_color_FFFFFF_t_70 1

这就是Java的反射机制,之所以反射机制能够获得Java类,类中方法等各种信息,就是因为上面说到的,JVM在内部为每一个类,Java方法都建立了函数模型,使得在程序运行过程中还可以动态获取类的信息,所以,method()对象做那么多事情,保存那么多Java方法的信息,除了提供字节码给JVM解释执行外,还有其他重要用途。

parameters()入参信息

parameters顾名思义就是Java方法的入参,JVM在call_stub()执行过程中调用CallStub函数指针,通过parameters参数解析出入参信息,再根据size_of_parameters(),即参数数量,来为方法分配栈空间,最后将参数逐个压栈。这里有一点要注意的是,在JVM的栈内存模型中,存放的只是变量的引用(也就是地址),而不是数据,这样做的好处是节省了很大的栈内存空间,前面日志写汇编函数调用过程中的栈分配时,可以看到,栈分配的大小并不大,通常为几MB,如果我们把栈空间分配的很大,当然可以,但是如果JVM为每一个方法栈都分配较大的空间,遇到递归调用之类的,或是方法里调用深度很深的情况,那么栈空间将会消耗掉很大的内存,容易造成内存溢出问题,这也是为什么JVM选择在方法栈空间中保存变量,例如Java类的实例对象时,存放的是其引用。

CallStub函数指针中的parameters()参数描述了Java方法的入参信息,例如名字,类型,size_of_parameters()标识了参数的数量,结合起来,因为JVM内存模型中存放的是参数的引用,只有知道了参数类型,还有参数数量,最后才能计算出方法栈需要的空间大小。

entry_point入口

entry_point参数可以说是CallStub函数指针中最重要的参数了,它表示的是JVM调用Java方法的入口,凡是调用Java方法,都要先经过该入口,执行这段机器指令,之后才能跳转到Java方法对应的机器指令执行,该入口的机器指令在JVM初始化时就会生成。entry_point做的事情是从method()中获取执行Java方法的字节码,JVM通过其获得需要执行的方法第一个字节码后,就可以开始解释执行:

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2p1c3RpbnplbmdUTQ_size_16_color_FFFFFF_t_70 2

可以看到,JVM在调用某一Java方法时,先通过调用CallStub函数指针,在函数指针中,所调用的函数,也就是地址,就是_call_stub_entry,前面说过,它是一个unsigned int类型,保存的是地址,JVM通过其所存放的函数地址,去调用函数。但是现在JVM只是找到了待调用函数的地址,也就是它在哪个位置,但是函数的可执行字节码还没有,所以在此之前还需要通过entry_point例程入口,得到待执行函数的字节码指令,最后才能真正地执行函数,即我们的Java方法。由上图看到_call_stub_entry和entry_point都是例程类型入口,例程就是JVM为我们写好的一些逻辑处理指令,例如函数调用和返回,异常处理等,因为Java是高级语言,需要解释成机器能读懂执行的语言,就需要通过这些中间方帮忙,还记得前几篇日志里写的JVM执行引擎吗?就是用的C语言实现,体现如何将Java这类高级语言翻译成机器指令,让CPU执行。这些例程都是JVM在初始化时就为我们写好了的。

发表评论

表情:
评论列表 (有 0 条评论,93人围观)

还没有评论,来说两句吧...

相关阅读