Hotspot源码解析-第27章-开始执行Java代码

第27章-开始执行Java代码

截止第26章完成了 创建Java虚拟的整个过程(Threads::create_vm()),在第5章时,有对后续流程的一个简单介绍,从这一章开始,要对后续流程做细节介绍,那就接着第5章继续讲。

27.1 获取Java所需环境

27.1.1 jni.cpp

27.1.1 .1JNI_CreateJavaVM

这个函数的大部分功能在第5章已经描述了轮廓,并且在第6章第26章,完成了细节讲解,这一小节,主要是在完成之后,拿到Java所需要的东西,看下面代码,只有几行是我们需要的:

// 这行的宏定义,就把它想像成一个返回jint的函数:jint  JNI_CreateJavaVM(JavaVM **vm, void **penv, void *args) {}
_JNI_IMPORT_OR_EXPORT_ jint JNICALL JNI_CreateJavaVM(JavaVM **vm, void **penv, void *args) {// 上面部分代码省略。。。。。。。result = Threads::create_vm((JavaVMInitArgs*) args, &can_try_again);if (result == JNI_OK) {  // 创建成功JavaThread *thread = JavaThread::current(); // 当前线程,其实就是主线程/* thread is thread_in_vm here */*vm = (JavaVM *)(&main_vm); // 将vm指针指向create_vm中创建的vm,后续操作要用// 就这行最重要,这里所谓的 jni_environment 就是jni要用的函数数组jni_NativeInterface,这个数组在jni.cpp文件中定义,里面存放了很多 Java 环境要用的函数,这些代码太长了,就不复制了,我截了一小段代码,看`图27-1`和`图27-2`*(JNIEnv**)penv = thread->jni_environment(); // 下面部分代码省略。。。。。。。} else { // create_vm创建vm失败if (can_try_again) {  // 判断是否可以重试// 可重试就把safe_to_recreate_vm设置为1safe_to_recreate_vm = 1;}// 创建失败了,要重置vm_created*vm = 0;*(JNIEnv**)penv = 0;// 释放创建过程vm_created空间OrderAccess::release_store(&vm_created, 0);}// 返回结果return result;
}

图27-1
在这里插入图片描述

图27-2
在这里插入图片描述

好了,截止到这整个VM环境也创建好了,Java环境需要的东西,也拿到了,接下来就要开始执行Java代码及后续流程了。这块入口在java.c->JavaMain()函数,该函数在第4章中讲过,那么再顺着内容继续讲。

27.2 拿到Java应用的 main 主类

27.2.1 java.c

27.2.1.1 LoadMainClass
/*函数的3个参数简单介绍一下:* env : JNIEnv指针* mode : 执行方式(主类和Jar文件)* name : 执行的java/jar文件名
*/
static jclass
LoadMainClass(JNIEnv *env, int mode, char *name)
{jmethodID mid;jstring str;jobject result;jlong start = 0, end = 0;// 获取Java中的 sun.launcher.LauncherHelper 类的Class,看名字就知道,这是一个启动Java应用的辅助类,怎么获取看`章节27.2.1.2`入口,然后再调用 `章节27.3`jclass cls = GetLauncherHelperClass(env);NULL_CHECK0(cls);if (JLI_IsTraceLauncher()) {start = CounterGet();}// 获取 sun.launcher.LauncherHelper 类的 checkAndLoadMain 静态方法,细节看`章节27.4`NULL_CHECK0(mid = (*env)->GetStaticMethodID(env, cls,"checkAndLoadMain","(ZILjava/lang/String;)Ljava/lang/Class;"));// 调用 sun.launcher.LauncherHelper 类的 checkAndLoadMain 静态方法,细节看`章节27.5`,最终拿到主类 appClass,checkAndLoadMain 方法的具体内容可以看`章节27.2.3`str = NewPlatformString(env, name);CHECK_JNI_RETURN_0(result = (*env)->CallStaticObjectMethod(env, cls, mid, USE_STDERR, mode, str));if (JLI_IsTraceLauncher()) {end = CounterGet();printf("%ld micro seconds to load main class\n",(long)(jint)Counter2Micros(end-start));printf("----%s----\n", JLDEBUG_ENV_ENTRY);}// 将执行结果返回return (jclass)result;
}
27.2.1.2 GetLauncherHelperClass
jclass
GetLauncherHelperClass(JNIEnv *env)
{if (helperClass == NULL) {// 从名字可以看出,要从根加载器中取出 sun/launcher/LauncherHelper (也就是Java中的 sun.launcher.LauncherHelper 类)        NULL_CHECK0(helperClass = FindBootStrapClass(env,"sun/launcher/LauncherHelper"));}return helperClass;
}

27.2.2 java_md_common.c

jclass
FindBootStrapClass(JNIEnv *env, const char* classname)
{if (findBootClass == NULL) {// 到这里就是调用jvm.cpp文件中的函数了,调用 JVM_FindClassFromBootLoader,如果没找到,报告错误并返回NULL,dlsym 是根据符号从动态链接库找到对应的地址,即可以是函数也可以是变量,这里是指函数,也就是说最终返回 JVM_FindClassFromBootLoader 的函数指针findBootClass = (FindClassFromBootLoader_t *)dlsym(RTLD_DEFAULT,"JVM_FindClassFromBootLoader");if (findBootClass == NULL) {JLI_ReportErrorMessage(DLL_ERROR4,"JVM_FindClassFromBootLoader");return NULL;}}// 执行 JVM_FindClassFromBootLoader 函数,细节看`章节27.3`return findBootClass(env, classname);
}

27.2.3 sun.launcher.LauncherHelper.checkAndLoadMain

public static Class<?> checkAndLoadMain(boolean printToStderr,int mode,String what) {initOutput(printToStderr);// 拿到类名String cn = null;switch (mode) { // 模式case LM_CLASS: // 普通的类文件cn = what;break;case LM_JAR:  // jar包cn = getMainClassFromJar(what); // 从jar包中找到主类(Main-Class定义的)break;default:// should never happenthrow new InternalError("" + mode + ": Unknown launch mode");}// 将 / 替换为 .cn = cn.replace('/', '.');Class<?> mainClass = null;try {// 加载主类mainClass = scloader.loadClass(cn);} catch (NoClassDefFoundError | ClassNotFoundException cnfe) {if (System.getProperty("os.name", "").contains("OS X")&& Normalizer.isNormalized(cn, Normalizer.Form.NFD)) {try {// On Mac OS X since all names with diacretic symbols are given as decomposed it// is possible that main class name comes incorrectly from the command line// and we have to re-compose itmainClass = scloader.loadClass(Normalizer.normalize(cn, Normalizer.Form.NFC));} catch (NoClassDefFoundError | ClassNotFoundException cnfe1) {abort(cnfe, "java.launcher.cls.error1", cn);}} else {abort(cnfe, "java.launcher.cls.error1", cn);}}// 加载完成,设置主类appClass = mainClass;if (mainClass.equals(FXHelper.class) ||FXHelper.doesExtendFXApplication(mainClass)) {// Will abort() if there are problems with the FX runtimeFXHelper.setFXLaunchParameters(what, mode);return FXHelper.class;}// 判断主类,主要查对主类的main方法的修饰符和返回值做判断validateMainClass(mainClass);return mainClass;}

27.3 从根加载器获取指定的类

27.3.1 jvm.cpp

27.3.1.1 JVM_FindClassFromBootLoader
JVM_ENTRY(jclass, JVM_FindClassFromBootLoader(JNIEnv* env,const char* name))JVMWrapper2("JVM_FindClassFromBootLoader %s", name);// 类名不能为空,且长度不能超过最大 2^16 - 1 if (name == NULL || (int)strlen(name) > Symbol::max_length()) {return NULL;}// 将 name 转换成符号TempNewSymbol h_name = SymbolTable::new_symbol(name, CHECK_NULL);// 从已加载的类库中查找 h_name 类,如果查不到,就会加载,处理细节会在类加载一文中讲解Klass* k = SystemDictionary::resolve_or_null(h_name, CHECK_NULL);if (k == NULL) {return NULL; // 没找到,也加载不了,返回NULL}if (TraceClassResolution) {trace_class_resolution(k);}// 给当前类的Class类创建实例,并最终返回出去return (jclass) JNIHandles::make_local(env, k->java_mirror());
JVM_END

27.4 获取Java类的静态方法

27.4.1 jni.cpp

27.4.1.1 jni_GetStaticMethodID
JNI_ENTRY(jmethodID, jni_GetStaticMethodID(JNIEnv *env, jclass clazz,const char *name, const char *sig))JNIWrapper("GetStaticMethodID");
#ifndef USDT2DTRACE_PROBE4(hotspot_jni, GetStaticMethodID__entry, env, clazz, name, sig);
#else /* USDT2 */HOTSPOT_JNI_GETSTATICMETHODID_ENTRY(env, (char *) clazz, (char *) name, (char *)sig);
#endif /* USDT2 */// 看这一行,继续往下看`章节 27.4.1.2`jmethodID ret = get_method_id(env, clazz, name, sig, true, thread);
#ifndef USDT2DTRACE_PROBE1(hotspot_jni, GetStaticMethodID__return, ret);
#else /* USDT2 */HOTSPOT_JNI_GETSTATICMETHODID_RETURN((uintptr_t) ret);
#endif /* USDT2 */return ret;
JNI_END
27.4.1.2 get_method_id
static jmethodID get_method_id(JNIEnv *env, jclass clazz, const char *name_str,const char *sig, bool is_static, TRAPS) {// 检查性的工作,就是看方法名在不在符号表,防止重复创建const char *name_to_probe = (name_str == NULL)? vmSymbols::object_initializer_name()->as_C_string(): name_str;TempNewSymbol name = SymbolTable::probe(name_to_probe, (int)strlen(name_to_probe));TempNewSymbol signature = SymbolTable::probe(sig, (int)strlen(sig));// 找不到,就报错 java_lang_NoSuchMethodErrorif (name == NULL || signature == NULL) {THROW_MSG_0(vmSymbols::java_lang_NoSuchMethodError(), name_str);}// 检查是不是常规类,如果不是,就抛错if (java_lang_Class::is_primitive(JNIHandles::resolve_non_null(clazz))) {THROW_MSG_0(vmSymbols::java_lang_NoSuchMethodError(), name_str);}// 检查工作做完后,拿到该class 对应的类KlassHandle klass(THREAD,java_lang_Class::as_Klass(JNIHandles::resolve_non_null(clazz)));// 初始化该类,主要是对类进行链接和初始化工作,不做这些工作,类中就不可能有方法、属性等信息的真实地址klass()->initialize(CHECK_NULL);// 查找对应的方法地址Method* m;if (name == vmSymbols::object_initializer_name() ||name == vmSymbols::class_initializer_name()) {// Never search superclasses for constructorsif (klass->oop_is_instance()) {m = InstanceKlass::cast(klass())->find_method(name, signature);} else {m = NULL;}} else {m = klass->lookup_method(name, signature);if (m == NULL &&  klass->oop_is_instance()) {m = InstanceKlass::cast(klass())->lookup_method_in_ordered_interfaces(name, signature);}}if (m == NULL || (m->is_static() != is_static)) {THROW_MSG_0(vmSymbols::java_lang_NoSuchMethodError(), name_str);}// 返回方法idreturn m->jmethod_id();
}

27.5 JNI调用Java类的静态方法

27.5.1 jni.cpp

27.5.1.1 jni_invoke_static

前面已经知道,调用Java静态方法时用的是 CallStaticObjectMethod 函数,但是在jni.cpp文件中找不到这个函数的定义,实际这个函数的定义隐藏在下面这段宏定义中,这段宏定义还是比较复杂的,涉及层次太多,咱们就不一一宏展开,直接讲解最重要的部分,这个宏定义最终就是告诉我们,会通过 jni_invoke_static 函数去执行调用,那么我们就直接看这个函数吧。

#define DEFINE_CALLSTATICMETHOD(ResultType, Result, Tag \, EntryProbe, ResultProbe) \
\DT_RETURN_MARK_DECL_FOR(Result, CallStatic##Result##Method, ResultType \, ResultProbe);                               \
\
JNI_ENTRY(ResultType, \jni_CallStatic##Result##Method(JNIEnv *env, jclass cls, jmethodID methodID, ...)) \JNIWrapper("CallStatic" XSTR(Result) "Method"); \
\EntryProbe; \ResultType ret = 0;\DT_RETURN_MARK_FOR(Result, CallStatic##Result##Method, ResultType, \(const ResultType&)ret);\
\va_list args; \va_start(args, methodID); \JavaValue jvalue(Tag); \JNI_ArgumentPusherVaArg ap(methodID, args); \// 看这一行jni_invoke_static(env, &jvalue, NULL, JNI_STATIC, methodID, &ap, CHECK_0); \va_end(args); \ret = jvalue.get_##ResultType(); \return ret;\
JNI_END
static void jni_invoke_static(JNIEnv *env, JavaValue* result, jobject receiver, JNICallType call_type, jmethodID method_id, JNI_ArgumentPusher *args, TRAPS) {methodHandle method(THREAD, Method::resolve_jmethod_id(method_id));// Create object to hold arguments for the JavaCall, and associate it with// the jni parserResourceMark rm(THREAD);int number_of_parameters = method->size_of_parameters();JavaCallArguments java_args(number_of_parameters);args->set_java_argument_object(&java_args); // 设置方法参数assert(method->is_static(), "method should be static");// Fill out JavaCallArguments objectargs->iterate( Fingerprinter(method).fingerprint() );// Initialize result typeresult->set_type(args->get_ret_type());// 真正调用Java方法的地方,result 是返回值JavaCalls::call(result, method, &java_args, CHECK);// 转换结果类型,从vm转到java能识别的if (result->get_type() == T_OBJECT || result->get_type() == T_ARRAY) {result->set_jobject(JNIHandles::make_local(env, (oop) result->get_jobject()));}
}

27.6 执行Java主类main方法

27.6.1 java.c

27.6.1.1 JavaMain

下面是JavaMain函数的一小段代码,仅与本章节内容相关

// 这一步是拿到主类,在本章的前面几节已经讲过了
mainClass = LoadMainClass(env, mode, what);
CHECK_EXCEPTION_NULL_LEAVE(mainClass);// 正常情况下拿到mainClass,就可以后续工作了,但是这里为了兼容JavaFX应用,增加了这一步,实际上非JavaFX应用,GetApplicationClass 只是把mainClass 赋值给appClass,然后重新拿出来,也就是说没做啥
appClass = GetApplicationClass(env);
NULL_CHECK_RETURN_VALUE(appClass, -1); // 校验工作
// 这一步没有实现,留待子类完成
PostJVMInit(env, appClass, vm);
CHECK_EXCEPTION_LEAVE(1); // 校验工作
// 拿到主类的main方法,实现细节看`章节27.4`
mainID = (*env)->GetStaticMethodID(env, mainClass, "main","([Ljava/lang/String;)V");
CHECK_EXCEPTION_NULL_LEAVE(mainID); // 校验工作// 组装平台相关的参数
mainArgs = CreateApplicationArgs(env, argv, argc);
CHECK_EXCEPTION_NULL_LEAVE(mainArgs); // 校验// 调用 main 方法,实现细节看`章节27.5`
(*env)->CallStaticVoidMethod(env, mainClass, mainID, mainArgs);/** The launcher's exit code (in the absence of calls to* System.exit) will be non-zero if main threw an exception.*/
// 判断执行时有没有异常发生,有则返回1,并退出系统;无则返回0,正常返回
ret = (*env)->ExceptionOccurred(env) == NULL ? 0 : 1;
LEAVE();

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/438760.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

dvwa,xss反射型lowmedium

xss&#xff0c;反射型&#xff0c;low&&medium low发现xss本地搭建实操 medium作为初学者的我第一次接触比较浅的绕过思路 low 发现xss 本关无过滤 <script>alert(/xss/)</script> //或 <script>confirm(/xss/)</script> //或 <script&…

springboot整合日志处理Logback

引言 ​ springboot框架 集成日志 logback 日志 ​ Logback是由log4j创始人设计的又一个开源日志组件。目前&#xff0c;logback分为三个模块&#xff1a;logback-core&#xff0c;logback-classic和logback-access。是对log4j日志展示进一步改进! 日志的级别 All < Trace…

C++(6) 继承

文章目录 继承1. 继承1.1 什么是继承1.2 C 继承方式1.2.1 基本案例1.2.2 继承权限组合1.2.3 继承中构造函数的说法1.2.4 继承中析构函数的执行顺序1.2.5 继承中变量名称冲突问题1.2.6 继承中函数【重写】 继承 1. 继承 1.1 什么是继承 面向对象程序设计中最重要的一个概念是继…

硬件知识(2) 手机的传感器-sensor

#灵感# 看看小米在干啥 手机型号&#xff1a;Redmi Note 13 Pro&#xff0c;解读一下它宣传的手机卖点。 目录 宣传1&#xff1a;1/1.4" 大底&#xff0c;f/1.65 大光圈&#xff0c; 宣传2&#xff1a;支持 2 亿像素超清直出&#xff0c;分辨率高达 16320 x 12240 宣…

基于STM32的以太网通信协议选择与实现

在基于STM32的以太网通信中&#xff0c;主要涉及到选择合适的通信协议和实现对应的功能代码。常见的通信协议包括TCP/IP、UDP、HTTP等&#xff0c;选择合适的协议取决于具体应用需求。以下将介绍在STM32上进行以太网通信时&#xff0c;常用的通信协议选择以及对应功能代码的实现…

C++仿函数、万能头文件、transform学习

这是网上的一个代码,里面的一些东西以前没用过; #include <bits/stdc++.h> using namespace std;// A Functor class increment { private:int num; public:increment(int n) : num(n) { }int operator () (int arr_num) const {return num + arr_num;} };// Driver …

如何配置MacLinuxWindows环境变量

这里写目录标题 什么是环境变量什么是PATH为什么要配置环境变量 如何配置环境变量环境变量有哪些环境变量加载顺序环境变量加载详解 配置参考方法一&#xff1a; export PATHLinux环境变量配置方法二&#xff1a;vim ~/.bashrcLinux环境变量配置方法三&#xff1a;vim ~/.bash_…

macOS与Linux相互投屏的方法

很多人面对跨系统投屏都望而却步。其实只要找对方法&#xff0c;两台不同系统的电脑也可以相互投屏。 今天就来看看Linux系统和macOS系统如何相互投屏&#xff01; 第一步&#xff0c;将Linux系统电脑和macOS系统电脑连接同一网络。假设是macOS系统投屏到Linux系统&#xff0c;…

Docker版Jellyfin手动安装插件

Docker版Jellyfin手动安装插件 目录隐射&#xff0c;然后把下载的插件解压到目录中查询运行中的容器&#xff0c;拿到容器id切换到容器目录下 复制解压好的插件(整个文件夹)到容器中存放的插件目录中重启容器后就能在插件中看到了 目录隐射&#xff0c;然后把下载的插件解压到目…

用可视化案例讲Rust编程3. 函数分解与参数传递

上一节我们在绘制面要素的时候&#xff0c;发现了函数功能体是三个不同步骤组成的&#xff1a; 读取文件获得geometry把geometry转变为绘图元素trace把绘图元素绘制到地图上 像我们上一节那样&#xff0c;把所有的功能都写在一个函数里面&#xff0c;这样的函数灵活性太差&am…

防御保护第四次作业

防火墙的智能选路 就近选路 --- 我们希望在访问不同运营商的服务器是&#xff0c;通过对应运营商的链路。这样可以高 通信效率&#xff0c;避免绕路。 策略路由 -- PBR 传统的路由&#xff0c;仅基于数据包中的目标IP地址查找路由表。仅关心其目标&#xff0c;所以&#…

Linux中条件变量的使用及注意事项

条件变量 应用场景&#xff1a;生产者消费者问题(类似于出租车和乘客问题)必要性&#xff1a;为了实现等待某个资源&#xff0c;让线程休眠。提高运行效率函数如下&#xff1a; int pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex); int p…