关于Java虚拟机HotSpot

 更新时间:2021年11月03日 14:56:59   作者:鸠摩  
这篇文章主要介绍了关于Java虚拟机HotSpot,在Java类中的一些方法会被由C/C++编写的HotSpot虚拟机的C/C++函数调用,不过由于Java方法与C/C++函数的调用约定不同,所以并不能直接调用,需要JavaCalls::call()这个函数辅助调用,下面我们来看看文章对内容的具体介绍

我们写的主类中的main()方法是如何被Java虚拟机调用到的?在Java类中的一些方法会被由C/C++编写的HotSpot虚拟机的C/C++函数调用,不过由于Java方法与C/C++函数的调用约定不同,所以并不能直接调用,需要JavaCalls::call()这个函数辅助调用。(我把由C/C++编写的叫函数,把Java编写的叫方法,后续也会延用这样的叫法)如下图所示。

从C/C++函数中调用的一些Java方法主要有:

  • (1)Java主类中的main()方法;
  • (2)Java主类装载时,调用JavaCalls::call()函数执行checkAndLoadMain()方法;
  • (3)类的初始化过程中,调用JavaCalls::call()函数执行的Java类初始化方法<clinit>,可以查看JavaCalls::call_default_constructor()函数,有对<clinit>方法的调用逻辑;
  • (4)我们先省略main方法的执行流程(其实main方法的执行也是先启动一个JavaMain线程,套路都是一样的),单看某个JavaThread的启动过程。JavaThread的启动最终都要通过一个native方法java.lang.Thread#start0()方法完成的,这个方法经过解释器的native_entry入口,调用到了JVM_StartThread()函数。其中的static void thread_entry(JavaThread* thread, TRAPS)函数中会调用JavaCalls::call_virtual()函数。JavaThread最终会通过JavaCalls::call_virtual()函数来调用字节码中的run()方法;
  • (5)在SystemDictionary::load_instance_class()这个能体现双亲委派的函数中,如果类加载器对象不为空,则会调用这个类加载器的loadClass()函数(通过call_virtual()函数来调用)来加载类。

当然还会有其它方法,这里就不一一列举了。通过JavaCalls::call()JavaCalls::call_helper()等函数调用Java方法,这些函数定义在JavaCalls类中,

这个类的定义如下:

从C/C++函数中调用的一些Java方法主要有:

  • (1)Java主类中的main()方法;
  • (2)Java主类装载时,调用JavaCalls::call()函数执行checkAndLoadMain()方法;
  • (3)类的初始化过程中,调用JavaCalls::call()函数执行的Java类初始化方法<clinit>,可以查看JavaCalls::call_default_constructor()函数,有对<clinit>方法的调用逻辑;
  • (4)我们先省略main方法的执行流程(其实main方法的执行也是先启动一个JavaMain线程,套路都是一样的),单看某个JavaThread的启动过程。JavaThread的启动最终都要通过一个native方法java.lang.Thread#start0()方法完成的,这个方法经过解释器的native_entry入口,调用到了JVM_StartThread()函数。其中的static void thread_entry(JavaThread* thread, TRAPS)函数中会调用JavaCalls::call_virtual()函数。JavaThread最终会通过JavaCalls::call_virtual()函数来调用字节码中的run()方法;
  • (5)在SystemDictionary::load_instance_class()这个能体现双亲委派的函数中,如果类加载器对象不为空,则会调用这个类加载器的loadClass()函数(通过call_virtual()函数来调用)来加载类。

当然还会有其它方法,这里就不一一列举了。通过JavaCalls::call()JavaCalls::call_helper()等函数调用Java方法,这些函数定义在JavaCalls类中,

这个类的定义如下:

源代码位置:openjdk/hotspot/src/share/vm/runtime/javaCalls.hpp
 
class JavaCalls: AllStatic {
  static void call_helper(JavaValue* result, methodHandle* method, JavaCallArguments* args, TRAPS);
 public:
  
  static void call_default_constructor(JavaThread* thread, methodHandle method, Handle receiver, TRAPS);
 
  // 使用如下函数调用Java中一些特殊的方法,如类初始化方法<clinit>等
  // receiver表示方法的接收者,如A.main()调用中,A就是方法的接收者
  static void call_special(JavaValue* result, KlassHandle klass, Symbol* name,Symbol* signature, JavaCallArguments* args, TRAPS);
  static void call_special(JavaValue* result, Handle receiver, KlassHandle klass,Symbol* name, Symbol* signature, TRAPS); 
  static void call_special(JavaValue* result, Handle receiver, KlassHandle klass,Symbol* name, Symbol* signature, Handle arg1, TRAPS);
  static void call_special(JavaValue* result, Handle receiver, KlassHandle klass,Symbol* name, Symbol* signature, Handle arg1, Handle arg2, TRAPS);
 
  // 使用如下函数调用动态分派的一些方法
  static void call_virtual(JavaValue* result, KlassHandle spec_klass, Symbol* name,Symbol* signature, JavaCallArguments* args, TRAPS);
  static void call_virtual(JavaValue* result, Handle receiver, KlassHandle spec_klass,Symbol* name, Symbol* signature, TRAPS); 
  static void call_virtual(JavaValue* result, Handle receiver, KlassHandle spec_klass,Symbol* name, Symbol* signature, Handle arg1, TRAPS);
  static void call_virtual(JavaValue* result, Handle receiver, KlassHandle spec_klass,Symbol* name, Symbol* signature, Handle arg1, Handle arg2, TRAPS);
 
  // 使用如下函数调用Java静态方法
  static void call_static(JavaValue* result, KlassHandle klass,Symbol* name, Symbol* signature, JavaCallArguments* args, TRAPS);
   static void call_static(JavaValue* result, KlassHandle klass,Symbol* name, Symbol* signature, TRAPS);
  static void call_static(JavaValue* result, KlassHandle klass,Symbol* name, Symbol* signature, Handle arg1, TRAPS);
  static void call_static(JavaValue* result, KlassHandle klass,Symbol* name, Symbol* signature, Handle arg1, Handle arg2, TRAPS);
 
  // 更低一层的接口,如上的一些函数可能会最终调用到如下这个函数
  static void call(JavaValue* result, methodHandle method, JavaCallArguments* args, TRAPS);
};


如上的函数都是自解释的,通过名称我们就能看出这些函数的作用。其中JavaCalls::call()函数是更低一层的通用接口。Java虚拟机规范定义的字节码指令共有5个,分别为invokestatic invokedynamicinvokestatic invokespecialinvokevirtual几种方法调用指令。这些call_static() call_virtual()函数内部调用了call()函数。这一节我们先不介绍各个方法的具体实现。下一篇将详细介绍。

我们选一个重要的main()方法来查看具体的调用逻辑。如下基本照搬R大的内容,不过我略做了一些修改,如下:

假设我们的Java主类的类名为JavaMainClass,下面为了区分java launcher里C/C++的main()Java层程序里的main(),把后者写作JavaMainClass.main()方法。

从刚进入C/C++的main()函数开始:

启动并调用HotSpot虚拟机的main()函数的线程执行的主要逻辑如下:

main()
-> //... 做一些参数检查
-> //... 开启新线程作为main线程,让它从JavaMain()函数开始执行;该线程等待main线程执行结束


在如上线程中会启动另外一个线程执行JavaMain()函数,如下:

JavaMain()
-> //... 找到指定的JVM
-> //... 加载并初始化JVM
-> //... 根据Main-Class指定的类名加载JavaMainClass
-> //... 在JavaMainClass类里找到名为"main"的方法,签名为"([Ljava/lang/String;)V",修饰符是public的静态方法
-> (*env)->CallStaticVoidMethod(env, mainClass, mainID, mainArgs); // 通过JNI调用JavaMainClass.main()方法


以上步骤都还在java launcher的控制下;当控制权转移到JavaMainClass.main()方法之后就没java launcher什么事了,等JavaMainClass.main()方法返回之后java launcher才接手过来清理和关闭JVM。

下面看一下调用Java主类main()方法时会经过的主要方法及执行的主要逻辑,如下:

// HotSpot VM里对JNI的CallStaticVoidMethod的实现。留意要传给Java方法的参数
// 以C的可变长度参数传入,这个函数将其收集打包为JNI_ArgumentPusherVaArg对象
-> jni_CallStaticVoidMethod()  
 
     // 这里进一步将要传给Java的参数转换为JavaCallArguments对象传下去    
     -> jni_invoke_static()  
       
        // 真正底层实现的开始。这个方法只是层皮,把JavaCalls::call_helper()
        // 用os::os_exception_wrapper()包装起来,目的是设置HotSpot VM的C++层面的异常处理
        -> JavaCalls::call()   
     
           -> JavaCalls::call_helper()
              -> //... 检查目标方法是否为空方法,是的话直接返回
              -> //... 检查目标方法是否“首次执行前就必须被编译”,是的话调用JIT编译器去编译目标方法
              -> //... 获取目标方法的解释模式入口from_interpreted_entry,下面将其称为entry_point
              -> //... 确保Java栈溢出检查机制正确启动
              -> //... 创建一个JavaCallWrapper,用于管理JNIHandleBlock的分配与释放,
                 // 以及在调用Java方法前后保存和恢复Java的frame pointer/stack pointer

              //... StubRoutines::call_stub()返回一个指向call stub的函数指针,
              // 紧接着调用这个call stub,传入前面获取的entry_point和要传给Java方法的参数等信息
              -> StubRoutines::call_stub()(...) 
                 // call stub是在VM初始化时生成的。对应的代码在
                 // StubGenerator::generate_call_stub()函数中
                 -> //... 把相关寄存器的状态调整到解释器所需的状态
                 -> //... 把要传给Java方法的参数从JavaCallArguments对象解包展开到解释模
                    // 式calling convention所要求的位置
                 -> //... 跳转到前面传入的entry_point,也就是目标方法的from_interpreted_entry

                    -> //... 在-Xcomp模式下,实际跳入的是i2c adapter stub,将解释模式calling convention
                       // 传入的参数挪到编译模式calling convention所要求的位置
                           -> //... 跳转到目标方法被JIT编译后的代码里,也就是跳到 nmethod 的 VEP 所指向的位置
                                -> //... 正式开始执行目标方法被JIT编译好的代码 <- 这里就是"main()方法的真正入口"

后面3个步骤是在编译执行的模式下,不过后续我们从解释执行开始研究,所以需要为虚拟机配置-Xint选项,有了这个选项后,Java主类的main()方法就会解释执行了。

在调用Java主类main()方法的过程中,我们看到了虚拟机是通过JavaCalls::call()函数来间接调用main()方法的,下一篇我们研究一下具体的调用逻辑。

到此这篇关于关于Java虚拟机HotSpot的文章就介绍到这了,更多相关Java虚拟机HotSpot内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 五种单件模式之Singleton的实现方法详解

    五种单件模式之Singleton的实现方法详解

    本篇文章是对Singleton的实现方法进行了详细的分析介绍,需要的朋友参考下
    2013-06-06
  • Java 输入多行字符串或者多个int数值的方法

    Java 输入多行字符串或者多个int数值的方法

    今天小编就为大家分享一篇Java 输入多行字符串或者多个int数值的方法,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2018-07-07
  • 带你了解Java数据结构和算法之高级排序

    带你了解Java数据结构和算法之高级排序

    这篇文章主要为大家介绍了Java数据结构和算法之高级排序,具有一定的参考价值,感兴趣的小伙伴们可以参考一下,希望能够给你带来帮助
    2022-01-01
  • Java设计模式之组合模式(Composite模式)介绍

    Java设计模式之组合模式(Composite模式)介绍

    这篇文章主要介绍了Java设计模式之组合模式(Composite模式)介绍,Composite定义:将对象以树形结构组织起来,以达成“部分-整体” 的层次结构,使得客户端对单个对象和组合对象的使用具有一致性,需要的朋友可以参考下
    2015-03-03
  • Spring框架 注解配置事务控制的流程

    Spring框架 注解配置事务控制的流程

    这篇文章主要介绍了Spring框架 注解配置事务控制的流程,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-09-09
  • spring boot Slf4j日志框架的体系结构详解

    spring boot Slf4j日志框架的体系结构详解

    在项目开发中记录日志是必做的一件事情,springboot内置了slf4j日志框架,下面这篇文章主要给大家介绍了关于spring boot Slf4j日志框架的体系结构,需要的朋友可以参考下
    2022-05-05
  • 关于Java集合框架Collection接口详解

    关于Java集合框架Collection接口详解

    这篇文章主要介绍了关于Java集合框架Collection接口详解,Collection接口是Java集合框架中的基础接口,定义了一些基本的集合操作,包括添加元素、删除元素、遍历集合等,需要的朋友可以参考下
    2023-05-05
  • Mybatis查询返回两个或多个参数问题

    Mybatis查询返回两个或多个参数问题

    这篇文章主要介绍了Mybatis查询返回两个或多个参数问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2023-06-06
  • 将Java程序与数据库进行连接的操作方法

    将Java程序与数据库进行连接的操作方法

    这篇文章主要介绍了将Java程序与数据库进行连接的操作方法,是Java入门学习中的基础知识,需要的朋友可以参考下
    2015-10-10
  • 基于javamelody监控springboot项目过程详解

    基于javamelody监控springboot项目过程详解

    这篇文章主要介绍了基于javamelody监控springboot项目过程详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2019-11-11

最新评论