• openjdk-jdk8-b109 hotspot虚拟机启动过程分析_1


    基于OpenJdk 标签 jdk8-b109版本分析

    HotSpot是由Oracle开发的Java虚拟机(JVM)的一种实现,也是使用最广泛的JVM之一。

    找到hotspot虚拟机入口

            完成编译配置后,会在根目录生成Makefile文件,如何编译jdk8  Windows平台编译Jdk8

    Makefile 中找到了 Main.gmk,构建入口由此开始。

    1. ifeq ($(words $(SPEC)),1)
    2. # We are building a single configuration. This is the normal case. Execute the Main.gmk file.
    3. include $(root_dir)/make/Main.gmk
    4. else

    根目录/make/Main.gmk,这里可以看到构建目标 :jdk,具体构建过程在 BuildJdk.gmk文件。

    1. jdk: langtools hotspot corba jaxp jaxws jdk-only
    2. jdk-only: start-make
    3. @$(call TargetEnter)
    4. @($(CD) $(JDK_TOPDIR)/make && $(BUILD_LOG_WRAPPER) $(MAKE) $(MAKE_ARGS) -f BuildJdk.gmk $(JDK_TARGET))
    5. @$(call TargetExit)

    根目录/jdk/make/BuildJdk.gmk,jvm启动器由构建目标 launchers 实现,由  CompileLaunchers.gmk 提供构建过程。

    1. launchers: libs launchers-only
    2. launchers-only:
    3. # Finally compile the launchers.
    4. +$(MAKE) -f CompileLaunchers.gmk

    最终定位到hotspot入口  根目录/jdk/src/share/bin/main.c

    1. $(call SetupNativeCompilation,BUILD_LAUNCHER_$1, \
    2. SRC := $(JDK_TOPDIR)/src/share/bin, \
    3. INCLUDE_FILES := main.c, \
    4. LANG := C, \

    进入jvm前-参数预处理

    jdk/src/share/bin/main.c

    main 并没有太多复杂的操作,它对程序参数解析并生成后续jvm可用的参数集合,然后调用 

    JLI_Launch 进一步处理。

    1. return JLI_Launch(margc, margv,
    2. sizeof(const_jargs) / sizeof(char *), const_jargs,
    3. sizeof(const_appclasspath) / sizeof(char *), const_appclasspath,
    4. FULL_VERSION,
    5. DOT_VERSION,
    6. (const_progname != NULL) ? const_progname : *margv,
    7. (const_launcher != NULL) ? const_launcher : *margv,
    8. (const_jargs != NULL) ? JNI_TRUE : JNI_FALSE,
    9. const_cpwildcard, const_javaw, const_ergo_class);

    JLI_Launch

    jdk/src/share/bin/java.c

    方法定义及参数描述清晰明了这里不再赘述。

    1. /*
    2. * Entry point.
    3. */
    4. int
    5. JLI_Launch(int argc, char ** argv, /* main argc, argc */
    6. int jargc, const char** jargv, /* java args */
    7. int appclassc, const char** appclassv, /* app classpath */
    8. const char* fullversion, /* full version defined */
    9. const char* dotversion, /* dot version defined */
    10. const char* pname, /* program name */
    11. const char* lname, /* launcher name */
    12. jboolean javaargs, /* JAVA_ARGS */
    13. jboolean cpwildcard, /* classpath wildcard*/
    14. jboolean javaw, /* windows-only javaw */
    15. jint ergo /* ergonomics class policy */
    16. )
    1. //设置跟踪调试标识
    2. InitLauncher(javaw);
    3. //如果启用了跟踪标识则输出启动参数
    4. if (JLI_IsTraceLauncher()) {
    5. int i;
    6. printf("Command line args:\n");
    7. for (i = 0; i < argc ; i++) {
    8. printf("argv[%d] = %s\n", i, argv[i]);
    9. }
    10. AddOption("-Dsun.java.launcher.diag=true", NULL);
    11. }

    选择JRE,并解析jvm参数、确认jre可使用。

    JLI_Launch->SelectVersion->LocateJRE->ExecJRE

    SelectVersion

    1. //保存jre版本信息
    2. if (version != NULL)
    3. info.jre_version = version;
    4. if (restrict_search != -1)
    5. info.jre_restrict_search = restrict_search;
    6. //入口类从命令行传入
    7. if (info.main_class != NULL)
    8. *main_class = JLI_StringDup(info.main_class);
    9. //没有jre版本信息,后续代码无法定位jre路径和进行版本检查
    10. if (info.jre_version == NULL) {
    11. JLI_FreeManifest();
    12. JLI_MemFree(new_argv);
    13. return;
    14. }
    15. //jre版本信息格式是否正确
    16. if (!JLI_ValidVersionString(info.jre_version)) {
    17. JLI_ReportErrorMessage(SPC_ERROR1, info.jre_version);
    18. exit(1);
    19. }
    20. 加载jre路径信息
    21. jre = LocateJRE(&info);
    22. //检查jre是否可使用,它会尝试启动jre并结束jre进程。
    23. ExecJRE(jre, new_argv);

    ExecJRE

    1. //尝试启动jre
    2. if (!CreateProcess((LPCTSTR)path, /* executable name */
    3. (LPTSTR)cmdline, /* command line */
    4. (LPSECURITY_ATTRIBUTES)NULL, /* process security attr. */
    5. (LPSECURITY_ATTRIBUTES)NULL, /* thread security attr. */
    6. (BOOL)TRUE, /* inherits system handles */
    7. (DWORD)0, /* creation flags */
    8. (LPVOID)NULL, /* environment block */
    9. (LPCTSTR)NULL, /* current directory */
    10. (LPSTARTUPINFO)&si, /* (in) startup information */
    11. (LPPROCESS_INFORMATION)&pi)) { /* (out) process information */
    12. JLI_ReportErrorMessageSys(SYS_ERROR1, path);
    13. exit(1);
    14. }
    15. if (WaitForSingleObject(pi.hProcess, INFINITE) != WAIT_FAILED) {
    16. if (GetExitCodeProcess(pi.hProcess, &exitCode) == FALSE)
    17. exitCode = 1;
    18. } else {
    19. JLI_ReportErrorMessage(SYS_ERROR2);
    20. exitCode = 1;
    21. }
    22. CloseHandle(pi.hThread);
    23. CloseHandle(pi.hProcess);

     CreateExecutionEnvironment 

    识别32位和64位模式

    检查虚拟机特性

    配置jvm路径 (jvm.dll)

    1. CreateExecutionEnvironment(&argc, &argv,
    2. jrepath, sizeof(jrepath),
    3. jvmpath, sizeof(jvmpath),
    4. jvmcfg, sizeof(jvmcfg));

     加载 jvm dll文件(jvm实现在dll文件中)

    1. if (!LoadJavaVM(jvmpath, &ifn)) {
    2. return(6);
    3. }

     加载特定版本的vcrt运行库

    加载jvm.dll

    获取 CreateJavaVM函数指针,jvm初始化入口在 CreateJavaVM

    1. LoadMSVCRT();
    2. /* Load the Java VM DLL */
    3. if ((handle = LoadLibrary(jvmpath)) == 0) {
    4. JLI_ReportErrorMessage(DLL_ERROR4, (char *)jvmpath);
    5. return JNI_FALSE;
    6. }
    7. /* Now get the function addresses */
    8. ifn->CreateJavaVM =
    9. (void *)GetProcAddress(handle, "JNI_CreateJavaVM");
    10. ifn->GetDefaultJavaVMInitArgs =
    11. (void *)GetProcAddress(handle, "JNI_GetDefaultJavaVMInitArgs");
    12. if (ifn->CreateJavaVM == 0 || ifn->GetDefaultJavaVMInitArgs == 0) {
    13. JLI_ReportErrorMessage(JNI_ERROR1, (char *)jvmpath);
    14. return JNI_FALSE;
    15. }
    1. 配置jdk一些初始化配置参数
    2. /* set the -Dsun.java.command pseudo property */
    3. SetJavaCommandLineProp(what, argc, argv);
    4. /* Set the -Dsun.java.launcher pseudo property */
    5. SetJavaLauncherProp();
    6. /* set the -Dsun.java.launcher.* platform properties */
    7. SetJavaLauncherPlatformProps();

    JVMInit->ContinueInNewThread->JavaMain

    ContinueInNewThread 只是创建一个线程并执行JavaMain函数。

    虚拟机启动入口

    JavaMain

    1. int JNICALL
    2. JavaMain(void * _args)
    3. {
    4. //参数拷贝
    5. JavaMainArgs *args = (JavaMainArgs *)_args;
    6. int argc = args->argc;
    7. char **argv = args->argv;
    8. int mode = args->mode;
    9. char *what = args->what;
    10. InvocationFunctions ifn = args->ifn;
    11. JavaVM *vm = 0; //虚拟机数据结构指针
    12. JNIEnv *env = 0; //虚拟机运行环境
    13. jclass mainClass = NULL;
    14. jclass appClass = NULL; // actual application class being launched
    15. jmethodID mainID;
    16. jobjectArray mainArgs;
    17. int ret = 0;
    18. jlong start, end;
    19. RegisterThread(); //windows平台是空函数
    20. //InitializeJVM 调用虚拟机真正的入口函数
    21. /**
    22. InitializeJVM->CreateJavaVM 完成虚拟机初始化及启动
    23. 初始化堆
    24. 全局数据结构(锁、GC子系统)
    25. 启动类库加载等等
    26. */
    27. start = CounterGet();
    28. if (!InitializeJVM(&vm, &env, &ifn)) {
    29. JLI_ReportErrorMessage(JVM_ERROR1);
    30. exit(1);
    31. }
    32. if (showSettings != NULL) {
    33. ShowSettings(env, showSettings);
    34. CHECK_EXCEPTION_LEAVE(1);
    35. }
    36. // java -version 输出版本信息后退出。
    37. if (printVersion || showVersion) {
    38. PrintJavaVersion(env, showVersion);
    39. CHECK_EXCEPTION_LEAVE(0);
    40. if (printVersion) {
    41. LEAVE();
    42. }
    43. }
    44. //没有指定jar或者入口类
    45. if (printXUsage || printUsage || what == 0 || mode == LM_UNKNOWN) {
    46. PrintUsage(env, printXUsage);
    47. CHECK_EXCEPTION_LEAVE(1);
    48. LEAVE();
    49. }
    50. FreeKnownVMs(); /* after last possible PrintUsage() */
    51. //启动虚拟机花了多长时间
    52. if (JLI_IsTraceLauncher()) {
    53. end = CounterGet();
    54. JLI_TraceLauncher("%ld micro seconds to InitializeJVM\n",
    55. (long)(jint)Counter2Micros(end-start));
    56. }
    57. //打印虚拟机环境参数
    58. if (JLI_IsTraceLauncher()){
    59. int i;
    60. printf("%s is '%s'\n", launchModeNames[mode], what);
    61. printf("App's argc is %d\n", argc);
    62. for (i=0; i < argc; i++) {
    63. printf(" argv[%2d] = '%s'\n", i, argv[i]);
    64. }
    65. }
    66. ret = 1;
    67. //加载入口类
    68. mainClass = LoadMainClass(env, mode, what);
    69. CHECK_EXCEPTION_NULL_LEAVE(mainClass);
    70. //JavaFX类应用使用,它没有main入口类。
    71. appClass = GetApplicationClass(env);
    72. NULL_CHECK_RETURN_VALUE(appClass, -1);
    73. //根据不同的java应用类初始化入口类
    74. PostJVMInit(env, appClass, vm);
    75. //获取 main方法ID
    76. mainID = (*env)->GetStaticMethodID(env, mainClass, "main",
    77. "([Ljava/lang/String;)V");
    78. CHECK_EXCEPTION_NULL_LEAVE(mainID);
    79. //要传给main方法的参数
    80. mainArgs = CreateApplicationArgs(env, argv, argc);
    81. CHECK_EXCEPTION_NULL_LEAVE(mainArgs);
    82. 调用main方法,我们的应用由此启动
    83. (*env)->CallStaticVoidMethod(env, mainClass, mainID, mainArgs);
    84. //main方法返回了
    85. ret = (*env)->ExceptionOccurred(env) == NULL ? 0 : 1;
    86. //销毁虚拟机、清理资源(堆、线程、各个子系统)、退出进程、异常退出则需要dump内存
    87. LEAVE();
    88. }

          虚拟机初始化及启动在 InitializeJVM 中,jvm初始化过程很比较复杂了,涉及到几乎所有的jvm子系统,后续继续分解它。

        //InitializeJVM 调用虚拟机真正的入口函数
        /**
        InitializeJVM->CreateJavaVM 完成虚拟机初始化及启动
        初始化堆
        全局数据结构(锁、GC子系统)
        启动类库加载等等
        */

            由此我们的应用正式运行了
        (*env)->CallStaticVoidMethod(env, mainClass, mainID, mainArgs);

  • 相关阅读:
    C# Promise对象详解
    一张图搞定CSS选择器的优先级
    信息系统项目管理师第四版学习笔记——组织通用管理
    Framework 为何被称为 Android 开发者必修?
    C++通过VS2022使用Conan2.0安装fmt库实现控制台彩色打印
    方法的注意事项
    从IoTDB的发展回顾时序数据库演进史
    DOM 总结
    高效掌握JDBC技术(二)| 掌握ORM思想 | 定义连接数据库的工具类
    Harbour.Space Scholarship Contest 2023-2024 (Div. 1 + Div. 2)C. Divisor Chain
  • 原文地址:https://blog.csdn.net/vs2008ASPNET/article/details/133882704