深入Java虚拟机笔记(五):剖析HotSpot的Launcher
来源:互联网 发布:阿里云先知 编辑:程序博客网 时间:2024/05/16 00:53
介绍
HotSpot属于OpenJDK项目的一个功能子集,HotSpot目录下四大子目录:
agent:包含Serviceability Agent的客户端的实现
make:用于build出HotSpot的各种配置文件
src:包括HotSpot的所有源码
test:单元测试
HotSpot VM源码目录结构
├─agent Serviceability Agent的实现
├─make 用来build出HotSpot的各种配置文件
├─src HotSpot VM的源代码
│ ├─cpu CPU相关代码
│ ├─os 操作系相关代码
│ ├─os_cpu 操作系统+CPU的组合相关的代码
│ └─share 平台无关的共通代码
│ ├─tools 工具
│ │ ├─hsdis 反汇编插件
│ │ ├─IdealGraphVisualizer 将server编译器的中间代码可视化的工具
│ │ ├─launcher 启动程序“java”
│ │ ├─LogCompilation 将-XX:+LogCompilation输出的日志(hotspot.log)整理成更容易阅读的格式的工具
│ │ └─ProjectCreator 生成Visual Studio的project文件的工具
│ └─vm HotSpot VM的核心代码
│ ├─adlc 平台描述文件(上面的cpu或os_cpu里的*.ad文件)的编译器
│ ├─asm 汇编器接口
│ ├─c1 client编译器
│ ├─ci 动态编译器的公共服务/接口
│ ├─classfile 类文件的处理(包括类加载和系统符号表等)
│ ├─code 动态生成的代码的管理
│ ├─compiler 编译器接口
│ ├─gc_implementation GC的实现
│ │ ├─concurrentMarkSweep Concurrent Mark Sweep GC的实现
│ │ ├─g1 Garbage-First GC的实现(不使用老的分代式GC框架)
│ │ ├─parallelScavenge ParallelScavenge GC的实现(server VM默认,不使用老的分代式GC框架)
│ │ ├─parNew ParNew GC的实现
│ │ └─shared GC的共通实现
│ ├─gc_interface GC的接口
│ ├─interpreter 解释器,包括“模板解释器”(官方版在用)和“C++解释器”(官方版不在用)
│ ├─libadt 一些抽象数据结构
│ ├─memory 内存管理相关(老的分代式GC框架也在这里)
│ ├─oops HotSpot VM的对象系统的实现
│ ├─opto server编译器
│ ├─prims HotSpot VM的对外接口,包括部分标准库的native部分和JVMTI实现
│ ├─runtime 运行时支持库(包括线程管理、编译器调度、锁、反射等)
│ ├─services 主要是用来支持JMX之类的管理功能的接口
│ ├─shark 基于LLVM的JIT编译器(官方版里没有使用)
│ └─utilities 一些基本的工具类
└─test 单元测试
Launcher是一直用于启动JVM进程的启动器,有两种,
一种windows平台下运行时会保留在控制台
一种用于执行Java的GUI程序,不会显示任何程序的输出信息
Launcher只是一个封装了虚拟机的执行外壳,由它负责装载JRE环境和windows平台下的jvm.dll动态链接库
Launcher的执行过程
1、启动函数main()
(1)Launcher启动后,对与运行环境有关的局部变量进行初始化。该局部变量在后面创建运行环境需要用到,在调用JavaMain()函数也需要传递过去
char *jarfile = 0; char *classname = 0; char *s = 0; char *main_class = NULL; int ret; InvocationFunctions ifn; jlong start, end; char jrepath[MAXPATHLEN], jvmpath[MAXPATHLEN]; char ** original_argv = argv;
(2)创建运行环境
CreateExecutionEnvironment(&argc, &argv, jrepath, sizeof(jrepath), jvmpath, sizeof(jvmpath), original_argv); printf("Using java runtime at: %s\n", jrepath);
(3)在函数程序末尾,创建一个新的线程去执行JVM的初始化和正式调用Java程序main()方法
struct JavaMainArgs args; args.argc = argc; args.argv = argv; args.jarfile = jarfile; args.classname = classname; args.ifn = ifn; return ContinueInNewThread(JavaMain, threadStackSize, (void*)&args);
2、在主线程中执行JavaMain()函数
当main()函数完成运行环境的创建后,接下来就任务交给在主线程中执行JavaMain()函数,JavaMain()函数会从Main()函数中传递过来的一些变量参数,然后初始化几个比较重要的局部变量
//从Main()函数中传递过来的一些变量参数struct JavaMainArgs *args = (struct JavaMainArgs *)_args; int argc = args->argc; char **argv = args->argv; char *jarfile = args->jarfile; char *classname = args->classname; InvocationFunctions ifn = args->ifn;//初始化几个比较重要的局部变量 JavaVM *vm = 0; JNIEnv *env = 0; jstring mainClassName; jclass mainClass; jmethodID mainID; jobjectArray mainArgs; int ret = 0; jlong start, end;
结构体JavaMainArgs如下
struct JavaMainArgs { int argc; char ** argv; char * jarfile; char * classname; InvocationFunctions ifn;};
结构体JavaMainArgs内部的 InvocationFunctions类型中包含的函数指针与对应的目标函数,如下:
ifn.CreateJavaVM = 0;ifn.GetDefaultJavaVMInitArgs = 0;
函数指针所指向的目标函数主要用于完成断开主线程和执行JVM销毁等功能,如下:
(*vm)->DetachCurrentThread(vm)(*vm)->DestroyJavaVM(vm)
当函数指针成功指向目标函数和JVM初始化后,Launcher会执行LoadClass()函数和GetStaticMethodID()函数
//LoadClass()用于获取Java程序的启动类 mainClass = LoadClass(env, classname); //GetStaticMethodID()用于获取Java程序的启动方法 mainID = (*env)->GetStaticMethodID(env, mainClass, "main", "([Ljava/lang/String;)V");
接下来,调用CallStaticVoidMethod()执行Java程序的Main()方法
(*env)->CallStaticVoidMethod(env, mainClass, mainID, mainArgs);
当Java程序执行完后,断开与主线程的连接
if ((*vm)->DetachCurrentThread(vm) != 0) { message = "Could not detach main thread."; messageDest = JNI_TRUE; ret = 1; goto leave; }int
当断开连接后,Launcher等待所有非守护线程全部执行完成,最后销毁JVM
(*vm)->DestroyJavaVM(vm);
附上相关源码
Main()函数全部源码
int main(int argc, char ** argv){ char *jarfile = 0; char *classname = 0; char *s = 0; char *main_class = NULL; int ret; InvocationFunctions ifn; jlong start, end; char jrepath[MAXPATHLEN], jvmpath[MAXPATHLEN]; char ** original_argv = argv; if (getenv("_JAVA_LAUNCHER_DEBUG") != 0) { _launcher_debug = JNI_TRUE; printf("----_JAVA_LAUNCHER_DEBUG----\n"); }#ifndef GAMMA /* * Make sure the specified version of the JRE is running. * * There are three things to note about the SelectVersion() routine: * 1) If the version running isn't correct, this routine doesn't * return (either the correct version has been exec'd or an error * was issued). * 2) Argc and Argv in this scope are *not* altered by this routine. * It is the responsibility of subsequent code to ignore the * arguments handled by this routine. * 3) As a side-effect, the variable "main_class" is guaranteed to * be set (if it should ever be set). This isn't exactly the * poster child for structured programming, but it is a small * price to pay for not processing a jar file operand twice. * (Note: This side effect has been disabled. See comment on * bugid 5030265 below.) */ SelectVersion(argc, argv, &main_class);#endif /* ifndef GAMMA */ /* copy original argv */ { int i; original_argv = (char**)JLI_MemAlloc(sizeof(char*)*(argc+1)); for(i = 0; i < argc+1; i++) original_argv[i] = argv[i]; } CreateExecutionEnvironment(&argc, &argv, jrepath, sizeof(jrepath), jvmpath, sizeof(jvmpath), original_argv); printf("Using java runtime at: %s\n", jrepath); ifn.CreateJavaVM = 0; ifn.GetDefaultJavaVMInitArgs = 0; if (_launcher_debug) start = CounterGet(); if (!LoadJavaVM(jvmpath, &ifn)) { exit(6); } if (_launcher_debug) { end = CounterGet(); printf("%ld micro seconds to LoadJavaVM\n", (long)(jint)Counter2Micros(end-start)); }#ifdef JAVA_ARGS /* javac, jar and friends. */ progname = "java";#else /* java, oldjava, javaw and friends */#ifdef PROGNAME progname = PROGNAME;#else progname = *argv; if ((s = strrchr(progname, FILE_SEPARATOR)) != 0) { progname = s + 1; }#endif /* PROGNAME */#endif /* JAVA_ARGS */ ++argv; --argc;#ifdef JAVA_ARGS /* Preprocess wrapper arguments */ TranslateApplicationArgs(&argc, &argv); if (!AddApplicationOptions()) { exit(1); }#endif /* Set default CLASSPATH */ if ((s = getenv("CLASSPATH")) == 0) { s = "."; }#ifndef JAVA_ARGS SetClassPath(s);#endif /* * Parse command line options; if the return value of * ParseArguments is false, the program should exit. */ if (!ParseArguments(&argc, &argv, &jarfile, &classname, &ret, jvmpath)) { exit(ret); } /* Override class path if -jar flag was specified */ if (jarfile != 0) { SetClassPath(jarfile); } /* set the -Dsun.java.command pseudo property */ SetJavaCommandLineProp(classname, jarfile, argc, argv); /* Set the -Dsun.java.launcher pseudo property */ SetJavaLauncherProp(); /* set the -Dsun.java.launcher.* platform properties */ SetJavaLauncherPlatformProps();#ifndef GAMMA /* Show the splash screen if needed */ ShowSplashScreen();#endif /* * Done with all command line processing and potential re-execs so * clean up the environment. */ (void)UnsetEnv(ENV_ENTRY);#ifndef GAMMA (void)UnsetEnv(SPLASH_FILE_ENV_ENTRY); (void)UnsetEnv(SPLASH_JAR_ENV_ENTRY); JLI_MemFree(splash_jar_entry); JLI_MemFree(splash_file_entry);#endif /* * If user doesn't specify stack size, check if VM has a preference. * Note that HotSpot no longer supports JNI_VERSION_1_1 but it will * return its default stack size through the init args structure. */ if (threadStackSize == 0) { struct JDK1_1InitArgs args1_1; memset((void*)&args1_1, 0, sizeof(args1_1)); args1_1.version = JNI_VERSION_1_1; ifn.GetDefaultJavaVMInitArgs(&args1_1); /* ignore return value */ if (args1_1.javaStackSize > 0) { threadStackSize = args1_1.javaStackSize; } } { /* Create a new thread to create JVM and invoke main method */ struct JavaMainArgs args; args.argc = argc; args.argv = argv; args.jarfile = jarfile; args.classname = classname; args.ifn = ifn; return ContinueInNewThread(JavaMain, threadStackSize, (void*)&args); }}
JavaMain()函数全部源码
JavaMain(void * _args){ struct JavaMainArgs *args = (struct JavaMainArgs *)_args; int argc = args->argc; char **argv = args->argv; char *jarfile = args->jarfile; char *classname = args->classname; InvocationFunctions ifn = args->ifn; JavaVM *vm = 0; JNIEnv *env = 0; jstring mainClassName; jclass mainClass; jmethodID mainID; jobjectArray mainArgs; int ret = 0; jlong start, end; /* * Error message to print or display; by default the message will * only be displayed in a window. */ char * message = "Fatal exception occurred. Program will exit."; jboolean messageDest = JNI_FALSE; /* Initialize the virtual machine */ if (_launcher_debug) start = CounterGet(); if (!InitializeJVM(&vm, &env, &ifn)) { ReportErrorMessage("Could not create the Java virtual machine.", JNI_TRUE); exit(1); } if (printVersion || showVersion) { PrintJavaVersion(env); if ((*env)->ExceptionOccurred(env)) { ReportExceptionDescription(env); goto leave; } if (printVersion) { ret = 0; message = NULL; goto leave; } if (showVersion) { fprintf(stderr, "\n"); } } /* If the user specified neither a class name nor a JAR file */ if (jarfile == 0 && classname == 0) { PrintUsage(); message = NULL; goto leave; }#ifndef GAMMA FreeKnownVMs(); /* after last possible PrintUsage() */#endif if (_launcher_debug) { end = CounterGet(); printf("%ld micro seconds to InitializeJVM\n", (long)(jint)Counter2Micros(end-start)); } /* At this stage, argc/argv have the applications' arguments */ if (_launcher_debug) { int i = 0; printf("Main-Class is '%s'\n", classname ? classname : ""); printf("Apps' argc is %d\n", argc); for (; i < argc; i++) { printf(" argv[%2d] = '%s'\n", i, argv[i]); } } ret = 1; /* * Get the application's main class. * * See bugid 5030265. The Main-Class name has already been parsed * from the manifest, but not parsed properly for UTF-8 support. * Hence the code here ignores the value previously extracted and * uses the pre-existing code to reextract the value. This is * possibly an end of release cycle expedient. However, it has * also been discovered that passing some character sets through * the environment has "strange" behavior on some variants of * Windows. Hence, maybe the manifest parsing code local to the * launcher should never be enhanced. * * Hence, future work should either: * 1) Correct the local parsing code and verify that the * Main-Class attribute gets properly passed through * all environments, * 2) Remove the vestages of maintaining main_class through * the environment (and remove these comments). */ if (jarfile != 0) { mainClassName = GetMainClassName(env, jarfile); if ((*env)->ExceptionOccurred(env)) { ReportExceptionDescription(env); goto leave; } if (mainClassName == NULL) { const char * format = "Failed to load Main-Class manifest " "attribute from\n%s"; message = (char*)JLI_MemAlloc((strlen(format) + strlen(jarfile)) * sizeof(char)); sprintf(message, format, jarfile); messageDest = JNI_TRUE; goto leave; } classname = (char *)(*env)->GetStringUTFChars(env, mainClassName, 0); if (classname == NULL) { ReportExceptionDescription(env); goto leave; } mainClass = LoadClass(env, classname); if(mainClass == NULL) { /* exception occured */ const char * format = "Could not find the main class: %s. Program will exit."; ReportExceptionDescription(env); message = (char *)JLI_MemAlloc((strlen(format) + strlen(classname)) * sizeof(char) ); messageDest = JNI_TRUE; sprintf(message, format, classname); goto leave; } (*env)->ReleaseStringUTFChars(env, mainClassName, classname); } else { mainClassName = NewPlatformString(env, classname); if (mainClassName == NULL) { const char * format = "Failed to load Main Class: %s"; message = (char *)JLI_MemAlloc((strlen(format) + strlen(classname)) * sizeof(char) ); sprintf(message, format, classname); messageDest = JNI_TRUE; goto leave; } classname = (char *)(*env)->GetStringUTFChars(env, mainClassName, 0); if (classname == NULL) { ReportExceptionDescription(env); goto leave; } mainClass = LoadClass(env, classname); if(mainClass == NULL) { /* exception occured */ const char * format = "Could not find the main class: %s. Program will exit."; ReportExceptionDescription(env); message = (char *)JLI_MemAlloc((strlen(format) + strlen(classname)) * sizeof(char) ); messageDest = JNI_TRUE; sprintf(message, format, classname); goto leave; } (*env)->ReleaseStringUTFChars(env, mainClassName, classname); } /* Get the application's main method */ mainID = (*env)->GetStaticMethodID(env, mainClass, "main", "([Ljava/lang/String;)V"); if (mainID == NULL) { if ((*env)->ExceptionOccurred(env)) { ReportExceptionDescription(env); } else { message = "No main method found in specified class."; messageDest = JNI_TRUE; } goto leave; } { /* Make sure the main method is public */ jint mods; jmethodID mid; jobject obj = (*env)->ToReflectedMethod(env, mainClass, mainID, JNI_TRUE); if( obj == NULL) { /* exception occurred */ ReportExceptionDescription(env); goto leave; } mid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, obj), "getModifiers", "()I"); if ((*env)->ExceptionOccurred(env)) { ReportExceptionDescription(env); goto leave; } mods = (*env)->CallIntMethod(env, obj, mid); if ((mods & 1) == 0) { /* if (!Modifier.isPublic(mods)) ... */ message = "Main method not public."; messageDest = JNI_TRUE; goto leave; } } /* Build argument array */ mainArgs = NewPlatformStringArray(env, argv, argc); if (mainArgs == NULL) { ReportExceptionDescription(env); goto leave; } /* Invoke main method. */ (*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. */ ret = (*env)->ExceptionOccurred(env) == NULL ? 0 : 1; /* * Detach the main thread so that it appears to have ended when * the application's main method exits. This will invoke the * uncaught exception handler machinery if main threw an * exception. An uncaught exception handler cannot change the * launcher's return code except by calling System.exit. */ if ((*vm)->DetachCurrentThread(vm) != 0) { message = "Could not detach main thread."; messageDest = JNI_TRUE; ret = 1; goto leave; }int message = NULL; leave: /* * Wait for all non-daemon threads to end, then destroy the VM. * This will actually create a trivial new Java waiter thread * named "DestroyJavaVM", but this will be seen as a different * thread from the one that executed main, even though they are * the same C thread. This allows mainThread.join() and * mainThread.isAlive() to work as expected. */ (*vm)->DestroyJavaVM(vm); if(message != NULL && !noExitErrorMessage) ReportErrorMessage(message, messageDest); return ret;}
参考:《Java虚拟机精讲》
- 深入Java虚拟机笔记(五):剖析HotSpot的Launcher
- 剖析hotspot的launcher
- 剖析HotSpot的Launcher
- Java虚拟机HotSpot笔记
- 深入理解java虚拟机(五):hotspot垃圾收集算法实现
- 深入理解java虚拟机(五):hotspot垃圾收集算法实现
- 《深入理解Java虚拟机》:HotSpot虚拟机内的即时编译器
- HotSpot虚拟机的算法实现——深入理解Java虚拟机(八)
- 【深入理解Java虚拟机】------ 学习0304 HotSpot的算法实现
- 6.《深入理解Java虚拟机》HotSpot 的算法实现
- java HotSpot虚拟机垃圾回收优化(五、Available Collectors)
- 《深入理解java虚拟机》学习笔记05--HotSpot中对象存活判读算法和垃圾收集算法的实现
- 深入理解java虚拟机学习笔记(五)
- Java虚拟机(HotSpot)对象
- 深入理解Java虚拟机(二)——HotSpot虚拟机对象
- 探索深入理解java虚拟机之hotspot虚拟机对象(2)
- [深入理解Java虚拟机]第二章 HotSpot虚拟机对象探秘
- 深入理解Java虚拟机——HotSpot虚拟机对象探秘
- Unity3D:Camera的Clear Flags
- EMI滤波元件与滤波器的种类
- LinkHashMap实现LRU
- opencv 判断两张图片的相似度
- 阿里云ECS centos7配置jdk-tomcat-mysql 并部署javaweb项目
- 深入Java虚拟机笔记(五):剖析HotSpot的Launcher
- Android Studio隐藏标题栏和系统状态栏
- 线性代数常用基本知识 (含向量和矩阵范数<Matrix or vector norm>)
- 【Redis缓存机制】9.快照持久化和AOF持久化
- 链表翻转的图文讲解(递归与迭代两种实现)
- 前端开发中js小技巧
- 赏心悦目风景二
- Android Studio 导入Eclipse项目
- mysql innodb表压缩