JavaVM和JNIEnv的初始化和JVM各模块的初始化都是在JNI_CreateJavaVM()函数中完成。这一篇将详细介绍JavaVM和JNIEnv的初始化过程。

1、初始化JavaVM

JavaVM的初始化都是在JNI_CreateJavaVM()函数中完成,调用链如下:

JavaMain()            java.c
InitializeJVM() java.c
JNI_CreateJavaVM() jni.cpp

在JavaMain()函数中调用InitializeJVM()函数,InitializeJVM()函数会初始化JVM,给JavaVM和JNIEnv变量赋值,通过InvocationFunctions结构体下的CreateJavaVM函数指针来调用对应的函数。在执行LoadJavaVM()函数时,CreateJavaVM函数指针被指向为libjvm.so(在Linux系统上的动态链接库为libjvm.so)动态链接库中JNI_CreateJavaVM()函数。JNI_CreateJavaVM()函数的实现如下:

源代码位置:openjdk/hotspot/src/share/vm/jni.cpp

_JNI_IMPORT_OR_EXPORT_ jint JNICALL JNI_CreateJavaVM(JavaVM **vm, void **penv, void *args) {

  // ...
jint result = JNI_ERR; bool can_try_again = true;
//完成JVM的初始化,如果初始化过程中出现不可恢复的异常则can_try_again会被置为false
result = Threads::create_vm((JavaVMInitArgs*) args, &can_try_again); //初始化正常
if (result == JNI_OK) {
//获取当前线程,即执行create_vm()函数的线程,也是JNI_CreateJavaVM()函数执行完毕后执行main()方法的线程
JavaThread *thread = JavaThread::current(); // JavaVM赋值,main_vm是jni.h中的全局变量,最张会指向全局变量jni_InvokeInterface
*vm = (JavaVM *)(&main_vm); // vm是方法的参数,是个双重指针 //JNIEnv赋值,从这里也可以看出,JNIEnv其实是线程私有有
*(JNIEnv**)penv = thread->jni_environment(); // ...
}
// ...
return result;
}

调用Threads::create_vm()函数会初始化一系列的JVM模块,如果函数顺利执行完,则函数会返回JNI_OK,表示正确创建出了JVM实例,我们可以给vm赋值,这样JNI的本地函数就可以通过vm来管理这个创建出来的JVM实例了。

使用main_vm来初始化JavaVM类型的变量,main_vm变量的定义如下:

源代码来源:openjdk/hotspot/src/share/vm/jni.cpp

struct JavaVM_ main_vm = {  &jni_InvokeInterface };

JavaVM是JavaVM_的别名,所以我们后面提到的JavaVM指的就是JavaVM​_。同理,JNIEnv是JNIE​nv_的别名。​

jni_InvokeInterface的定义如下:

const struct JNIInvokeInterface_   jni_InvokeInterface = {
NULL,
NULL,
NULL, jni_DestroyJavaVM,
jni_AttachCurrentThread,
jni_DetachCurrentThread,
jni_GetEnv,
jni_AttachCurrentThreadAsDaemon
};

结构体中保存了一系列函数指针,我们大前面说的JNI的本地函数通过vm来管理JVM实例,其实就是通过函数指针来调用对应的函数管理JVM实例。

通过 JavaVM,我们还可以获得 JVMTI 的指针,并获得 JVMTI 函数的使用能力,所有的 JVMTI 函数都通过jvmtiEnv获取,不同的虚拟机实现提供的函数细节可能不一样,但是使用的方式是统一的,关于JVMTI及相关知识这里不介绍,有兴趣的可自行研究。 

2、初始化JNIEnv

JavaMain()                 java.c
InitializeJVM() java.c
JNI_CreateJavaVM() jni.cpp
Threads::create_vm() thread.cpp
JavaThread::JavaThread() thread.cpp
JavaThread::initialize() thread.cpp
jni_functions() jni.cpp

其中调用的Threads:create_vm()函数中会创建JavaThread实例,如下:

JavaThread* main_thread = new JavaThread()

根据调用链可知,在JavaThread构造函数中会调用JavaThread::initialize()函数,最终会调用jni_functions()函数。jni_functions()函数会返回thread.cpp文件中定义的一个全局变量,如下:

struct JNINativeInterface_* jni_functions() {
return &jni_NativeInterface;
}

jni_NativeInterface这个全局变量的定义如下:

struct JNINativeInterface_  jni_NativeInterface = {
NULL,f
NULL,
NULL,
NULL, jni_GetVersion,
jni_DefineClass,
jni_FindClass, ...
};

如上的结构体中保存了各个JNI函数的指针。

调用jni_functions()函数后还会调用如下函数:

void set_jni_functions(struct JNINativeInterface_* functionTable) {
_jni_environment.functions = functionTable;
}

其中_jni_environment变量的定义如下:

源代码位置:openjdk/hotspot/src/share/vm/rumtime/thread.hpp

// 定义在JavaThread中的变量,所以JNIEnv是线程私有的
JNIEnv _jni_environment;

JNIEnv这个结构体定义在之前说过,在这个结构体中首先定义的就是一个指向JNINativeInterface_的指针functions,如下:

struct JNIEnv_ {
const struct JNINativeInterface_ *functions;
...
}

_jni_environment就是main_thread保留的JNIEnv_实例了,正确设置functions属性就表示该实例初始化完成。

为了让大家一目了然,我简单画了一个示意图。

我们将创建出来的JNIEnv实例保存在线程的_jni_environment变量中,这样当线程执行本地函数时,会从这个变量中取出JNIEnv并传递给本地函数,我们得到JNIEnv后就能调用其中的函数来获取JVM内部提供的服务了,例如想要根据类名来查找jclass,可以调用FindClass()函数。

公众号 深入剖析Java虚拟机HotSpot 已经更新虚拟机源代码剖析相关文章到60+,欢迎关注,如果有任何问题,可加作者微信mazhimazh,拉你入虚拟机群交流

  

  

  

  

  

  

第41篇-JNIEnv与JavaVM的初始化的更多相关文章

  1. 第40篇-JNIEnv和JavaVM

    下面介绍2个与JNI机制相关的类型JNIEnv和JavaVM. 1.JNIEnv JNIEnv一般是是由虚拟机传入,而且与线程相关的变量,也就说线程A不能使用线程B的JNIEnv.而作为一个结构体,它 ...

  2. NDK(13)JNIEnv和JavaVM

    转自:  http://www.cnblogs.com/canphp/archive/2012/11/13/2768937.html JNIEnv是一个与线程相关的变量,不同线程的JNIEnv彼此独立 ...

  3. 【Android JNI】JNIEnv和JavaVM的区别

     JNI的实现可涉及两个关键类:JNIEnv和JavaVM. JavaVM:这个代表java的虚拟机.所有的工作都是从获取虚拟机的接口开始的.             第一种方式,在加载动态链接库的时 ...

  4. Jni 线程JNIEnv,JavaVM,JNI_OnLoad(GetEnv返回NULL?FindClass返回NULL?)

    此文章是关于NDK线程的第二篇理论知识笔记.主要有两个点,如下: 1.pthread_create(Too many arguements, expected 1) ?2.线程中如何获取JNIEnv? ...

  5. Java中JNI的使用详解第二篇:JNIEnv类型和jobject类型的解释

    上一篇说的是一个简单的应用,说明JNI是怎么工作的,这一篇主要来说一下,那个本地方法sayHello的参数的说明,以及其中方法的使用 首先来看一下C++中的sayHello方法的实现: JNIEXPO ...

  6. Java中JNI的使用详解第三篇:JNIEnv类型中方法的使用

    转自: http://blog.csdn.net/jiangwei0910410003/article/details/17466369 上一篇说道JNIEnv中的方法的用法,这一篇我们就来通过例子来 ...

  7. spring boot 学习番外篇:超快速项目初始化

    超快速完成 Spring Boot 项目初始化 最近,在浏览 SPRING 官网时,发现一个超级方便的小工具,可以帮助我们快速创建一个 Spring Boot 项目,前提就是你能连接互联网. 依赖 支 ...

  8. 第41篇 推荐一个jekyll博客模板

    本人用的模板是基于Codeboy的博客模板改造模板,(由于本人可能会有很多样式修改,所以不再将修改pullrequst到原项目,在此对codeboy模板表示感谢).功能改造如下: 添加微信支付宝打赏 ...

  9. NDK(5) Android JNI官方综合教程[JavaVM and JNIEnv,Threads ,jclass, jmethodID, and jfieldID,UTF-8 and UTF-16 Strings,Exceptions,Native Libraries等等]

    JNI Tips In this document JavaVM and JNIEnv Threads jclass, jmethodID, and jfieldID Local and Global ...

随机推荐

  1. Java(22)常用API一

    1 API 1.1 API概述 什么是API ​ API (Application Programming Interface) :应用程序编程接口 java中的API ​ 指的就是 JDK 中提供的 ...

  2. Java(30)集合五Set

    作者:季沐测试笔记 原文地址:https://www.cnblogs.com/testero/p/15228440.html 博客主页:https://www.cnblogs.com/testero ...

  3. 【实验向】问题:假设计算机A和计算机B通信,计算机A给计算机B发送一串16个字节的二进制字节串,以数组形式表示:

    问题: 假设计算机A和计算机B通信,计算机A给计算机B发送一串16个字节的二进制字节串,以数组形式表示: unsigned char[16] = {0x3f, 0xa0, 0x00, 0x00, 0x ...

  4. 保护模式篇——TLB与CPU缓存

    写在前面   此系列是本人一个字一个字码出来的,包括示例和实验截图.由于系统内核的复杂性,故可能有错误或者不全面的地方,如有错误,欢迎批评指正,本教程将会长期更新. 如有好的建议,欢迎反馈.码字不易, ...

  5. SpringCloud微服务实战——搭建企业级开发框架(六):使用knife4j集成Swagger2接口文档

    knife4j是为集成Swagger生成api文档的增强解决方案,前后端Java代码以及前端Ui模块进行分离,在微服务架构下使用更加灵活, 提供专注于Swagger的增强解决方案,不同于只是改善增强前 ...

  6. Google Object detection配置与使用

    Google Object detection 前言: 本文记录了使用Google发布的Object detection(July 1st, 2019)接口,完成了对标注目标的检测.参考了很多博文,在 ...

  7. [敏捷软工团队博客]Beta设计和计划

    项目 内容 2020春季计算机学院软件工程(罗杰 任健) 博客园班级博客 作业要求 Beta设计和计划 我们在这个课程的目标是 在团队合作中锻炼自己 这个作业在哪个具体方面帮助我们实现目标 对Beta ...

  8. 用cmd命令行创建vue项目模板

    1.进入cmd命令行 输入存放项目的位置 2.通过vue create 项目名称 创建项目 3.选择Manually select features 4.通过空格选中第1.2.5.6.7.去掉8 4. ...

  9. hdu 1502 Regular Words(DP)

    题意: 一个单词X由{A,B,C}三种字母构成. A(X):单词X中A的个数.B(X),C(X)同理. 一个单词X如果是regular word必须满足A(X)=B(X)=C(X)且对于X的任意前缀有 ...

  10. WPF_05_路由事件

    路由事件 WPF用更高级的路由事件替换普通的.NET事件.路由事件具有更强传播能力,可在元素树中向上冒泡和向下隧道传播,并沿着传播路径被事件处理程序处理.与依赖属性一样,路由事件由只读的静态字段表示, ...