第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();