您的位置 首页 java

OpenJdk1.8笔记——java启动流程

本文记录openjdk1.8 启动过程流程梳理。

jdk java 的入口函数文件为openjdk\jdk\src\share\ bin \main.c中的main方法(window上为WinMain),然后调用jdk8u-dev/jdk/src/share/bin/java.c的JLI_Launch方法,启动一个jvm 虚拟机

程序入口

作用:程序入口
位置:openjdk\jdk\src\share\bin\main.c

 /*
 * Entry point.
 *///java虚拟机启动入口函数
#ifdef JAVAW   //window平台入口
 char  **__initenv;
int WINAPI
WinMain(HINSTANCE inst, HINSTANCE previnst, LPSTR cmdline, int cmdshow)
{
    int margc;
    char** margv;
    const jboolean const_javaw = JNI_TRUE;

    __initenv = _environ;

#else /* JAVAW */   //linux平台入口函数
int main(int argc, char **argv){
    int margc;   //参数个数
    char** margv;  //命令参数
    const jboolean const_javaw = JNI_FALSE;
#endif

#ifdef _WIN32
    {
        int i = 0;
        if (getenv(JLDEBUG_ENV_ENTRY) != NULL) {
            printf("Windows original main args:\n");
            for (i = 0 ; i < __argc ; i++) {
                printf("wwwd_args[%d] = %s\n", i, __argv[i]);
            }
        }
    }
    JLI_CmdToArgs(GetCommandLine());
    margc = JLI_GetStdArgc();
    // add one more to mark the end
    margv = (char **)JLI_MemAlloc((margc + 1) * ( sizeof (char *)));
    {
        int i = 0;
        StdArg *stdargs = JLI_GetStdArgs();
        for (i = 0 ; i < margc ; i++) {
            margv[i] = stdargs[i].arg;
        }
        margv[i] = NULL;
    }
#else /* *NIXES */    margc = argc;
    margv = argv;
#endif /* WIN32 */
    //调用JLI_Launch执行java的执行体逻辑,参考代码jdk8u-dev/jdk/src/share/bin/java.c
    return JLI_Launch(margc,  //命令参数个数
                   margv,     //参数数组
                   sizeof(const_jargs) / sizeof(char *), //java args参数个数 
                   const_jargs,  //java参数
                   sizeof(const_app classpath ) / sizeof(char *),  //classpath 数量
                   const_appclasspath, //classpath数量
                   FULL_VERSION, //完整的版本号
                   DOT_VERSION,  //版本号
                   (const_progname != NULL) ? const_progname : *margv, //程序名称
                   (const_launcher != NULL) ? const_launcher : *margv, //启动器名称
                   (const_jargs != NULL) ? JNI_TRUE : JNI_FALSE,  //默认为0
                   const_cpwildcard,  //是否支持扩展classpath  默认为true
                   const_javaw,  //window下true, 其他false
                   const_ergo_class);//运行模式  默认为DEFAULT_POLICY=0  其他包括NEVER_SERVER_CLASS、ALWAYS_SERVER_CLASS
}  

JLI_Launch

作用:java入口函数,解析参数、创建环境、加载jvm动态库
位置:jdk8u-dev/jdk/src/share/bin/java.c

 /*
 * Entry point. java的入口函数
 */int
JLI_Launch(int argc, char ** argv,              /* main argc, argc */        int jargc, const char** jargv,          /* java args */        int appclassc, const char** appclassv,  /* app classpath */        const char* fullversion,                /* full version defined */        const char* dotversion,                 /* dot version defined */        const char* pname,                      /* program name */        const char* lname,                      /* launcher name */        jboolean javaargs,                      /* JAVA_ARGS */        jboolean cpwildcard,                    /* classpath wildcard*/        jboolean javaw,                         /* windows-only javaw */        jint ergo                               /* ergonomics class policy */)
{
    int mode = LM_UNKNOWN; //启动模式  默认为0   其他参考:java.h 中LaunchMode枚举定义
    char *what = NULL;
    char *cpath = 0;
    char *main_class = NULL;    //带有main函数的class文件
    int ret;
    InvocationFunctions ifn;   // 函数指针 结构体包括:创建jvm、获取jvm启动参数、获取创建的jvm
    jlong start, end;          //启动结束时间
    char jvmpath[MAXPATHLEN];   //jvm路径
    char jrepath[MAXPATHLEN];   //jre路径
    char jvmcfg[MAXPATHLEN];    //jvm配置

    _fVersion = fullversion;
    _dVersion = dotversion;
    _launcher_name = lname;
    _program_name = pname;
    _is_java_args = javaargs;
    _wc_enabled = cpwildcard;
    _ergo_policy = ergo;

//初始化启动器,window下注册启动异常时弹出ui选择调试器
    InitLauncher(javaw);
    DumpState(); //判断是否定义环境变量_JAVA_LAUNCHER_DEBUG,是的话打印调试信息
    if (JLI_IsTraceLauncher()) {  //打印命令行参数
        int i;
        printf("Command line args:\n");
        for (i = 0; i < argc ; i++) {
            printf("argv[%d] = %s\n", i, argv[i]);
        }
        //增加配置参数
        AddOption("-Dsun.java.launcher.diag=true", NULL);
    }

    /*
     * 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.)
     */    //选择合适版本的jre,可以从jar包中读取META-INF/MAINFEST.MF中获取
    //查看mian_class
    SelectVersion(argc, argv, &main_class);

    //创建执行环境
    CreateExecutionEnvironment(&argc, 
                               &argv,
                               jrepath, 
                               sizeof(jrepath),
                               jvmpath, 
                               sizeof(jvmpath),
                               jvmcfg,  
                               sizeof(jvmcfg));

    //初始设置函数指针为无效指针
    ifn.CreateJavaVM = 0;
    ifn.GetDefaultJava vm InitArgs = 0;

    //如果时debug的话,记录启动时间
    if (JLI_IsTraceLauncher()) {
        start = CounterGet();
    }

    //加载java vm虚拟机
    //加载jvmpath(jdk/lib/amd64/server/libjvm.so),并初始化jvm的指针
    /**
    *   获取动态库中的JNI_CreateJavaVM、JNI_GetDefaultJavaVMInitArgs、JNI_GetCreatedJavaVMs入口地址
    *   填充到ifn结构体中
    */    if (!LoadJavaVM(jvmpath, &ifn)) {
        return(6);
    }

    //debug模式下记录加载java vm虚拟机时间
    if (JLI_IsTraceLauncher()) {
        end   = CounterGet();
    }

    //打印加载javavm时间
    JLI_TraceLauncher("%ld micro seconds to LoadJavaVM\n",
             (long)(jint)Counter2Micros(end-start));

    ++argv;
    --argc;

    //是否有参数
    if (IsJavaArgs()) {
        /* 解析参数 */        TranslateApplicationArgs(jargc, jargv, &argc, &argv);
        if (!AddApplicationOptions(appclassc, appclassv)) {
            return(1);
        }
    } else {
        /* 设置环境变量中的classpath路径 */        cpath = getenv("CLASSPATH");
        if (cpath == NULL) {
            cpath = ".";
        }
        SetClassPath(cpath);
    }

    /* 解析命令行参数,如果解析失败则程序退出.
     */    if (!Parse Arguments (&argc, &argv, &mode, &what, &ret, jrepath)) {
        return(ret);
    }

    /* 如果-jar参数指定的话,重写classpath值 */    if (mode == LM_JAR) {
        SetClassPath(what);     /* Override class path */    }

    /* set the -Dsun.java.command pseudo property */    SetJavaCommandLineProp(what, argc, argv);

    /* Set the -Dsun.java.launcher pseudo property */    SetJavaLauncherProp();

    /* set the -Dsun.java.launcher.* platform properties */    SetJavaLauncherPlatformProps();

    //进行一系列处理,最终创建一个jvm的虚拟机并条用int JNICALL JavaMain( void  * _args);运行入口函数
    return JVMInit(&ifn, threadStackSize, argc, argv, mode, what, ret);
}  

JVMInit

作用:执行虚拟机入口函数
位置:jdk8u-dev/jdk/src/solaris/bin/java_md_solinux.c

 int
JVMInit(InvocationFunctions* ifn, //函数指针
        jlong threadStackSize,    // 线程 栈大小   在AddOption函数中修改全局的threadStackSize变量值,通过-Xss修改
        int argc,                 //参数数目
        char **argv,              //参数
        int mode,                 //模式
        char *what,               //
        int ret)                  //返回值
{
    //根据存储在环境变量中的jar文件名和镜像文件名显示启动画面
    ShowSplashScreen();
    //创建线程执行代码逻辑
    return ContinueInNewThread(ifn, threadStackSize, argc, argv, mode, what, ret);
}  

ContinueInNewThread

作用:组织参数,执行真正的虚拟机入口函数
位置:jdk8u-dev/jdk/src/share/bin/java.c

 int
ContinueInNewThread(InvocationFunctions* ifn, //函数指针
                    jlong threadStackSize,    //线程栈大小
                    int argc, 
                    char **argv,
                    int mode, 
                    char *what, 
                    int ret)
{

    /*
     * 如果没有指定线程栈大小, 则会检查vm是否指定一个默认值.
     * 注意:虚拟机不再支持1.1,但是他会通过初始化参数返回一个默认的栈大小值.
     */    if (threadStackSize == 0) {
      struct JDK1_1InitArgs args1_1;
      memset((void*)&args1_1, 0, sizeof(args1_1));
      //指定版本号
      args1_1.version = JNI_VERSION_1_1;
      //通过虚拟机返回一个指定的参数值,该方法时从libjvm.so里面导出的函数指针
      ifn->GetDefaultJavaVMInitArgs(&args1_1);  /* 忽略返回值 */      if (args1_1.javaStackSize > 0) {
         //如果查询到有效值,则会修改全局定义的栈大小
         threadStackSize = args1_1.javaStackSize;
      }
    }

    {
      /* 创建一个新线程去创建jvm,然后调用main方法*/      JavaMainArgs args;
      int rslt;

      args.argc = argc;
      args.argv = argv;
      args.mode = mode;
      args.what = what;
      args.ifn = *ifn;

      /**/      rslt = ContinueInNewThread0(JavaMain, threadStackSize, (void*)&args);
      /* If the caller has deemed there is an error we
       * simply return that, otherwise we return the value of
       * the callee
       */      return (ret != 0) ? ret : rslt;
    }
}
  

ContinueInNewThread0

作用:尝试创建新线程执行代码逻辑,创建新线程失败则在当前线程执行代码逻辑
位置:jdk8u-dev/jdk/src/solaris/bin/java_md_solinux.c

 /*
 * 暂停当前线程,然后继续执行一个新的线程
 */int
ContinueInNewThread0(int (JNICALL *continuation)(void *), //线程入口函数,在这里具体执行的时JavaMain方法
                          jlong stack_size,               //线程栈大小,会使用该值创建线程
                          void * args) {                  //参数
    int rslt;
#ifdef __linux__                                          //linux代码块
    pthread_t tid;
    pthread_attr_t attr;
    pthread_attr_init(&attr);
    p thread _attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);

    if (stack_size > 0) {
      pthread_attr_setstacksize(&attr, stack_size);       //如果给定的线程栈大小值有效,则设置创建线程的栈大小,否则使用默认值
    }

    /* 调用线程库创建线程,并设定入口函数为JavaMain */    if (pthread_create(&tid, &attr, (void *(*)(void*))continuation, (void*)args) == 0) {
      void *  tmp ;
      pthread_join(tid, &tmp);                            //创建成功后,并等待线程的结束,主线程挂起
      rslt = (int)tmp;
    } else {
     /*
      * 如果因为某些条件导致创建线程失败,则在当前线程执行JavaMain方法
      */      rslt = continuation(args);
    }

    pthread_attr_destroy(&attr);
#else /* ! __linux__ */                                   //solaris系统
    thread_t tid;
    long flags = 0;
    if (thr_create(NULL, stack_size, (void *(*)(void *))continuation, args, flags, &tid) == 0) {
      void * tmp;
      thr_join(tid, NULL, &tmp);
      rslt = (int)tmp;
    } else {
      /* See above. Continue in current thread if thr_create() failed */      rslt = continuation(args);
    }
#endif /* __linux__ */    return rslt;
}  

JavaMain

作用:虚拟机的入口函数
位置:jdk8u-dev/jdk/src/share/bin/java.c

 int JNICALL
JavaMain(void * _args)
{
    JavaMainArgs *args = (JavaMainArgs *)_args;         //获取参数
    int argc = args->argc;
    char **argv = args->argv;
    int mode = args->mode;
    char *what = args->what;
    InvocationFunctions ifn = args->ifn;                //当前虚拟机导致的函数指针
                                                        //该机制可以保证同一环境配置多个jdk版本

    JavaVM *vm = 0;
    JNIEnv *env = 0;
    jclass mainClass = NULL;                            //main函数class
    jclass appClass = NULL;                             // 正在启动的实际应用程序类
    jmethodID mainID;
    jobjectArray mainArgs;
    int ret = 0;
    jlong start, end;

    RegisterThread();                                   //window/类unix为空实现,macos特别处理

    /* 初始化虚拟机 */    start = CounterGet();
    if (!InitializeJVM(&vm, &env, &ifn)) {              //初始化虚拟机,通过调用libvm.so
                                                        //导出的CreateJavaVM执行真正的
                                                        //创建虚拟机逻辑,最主要的时把env绑定到具体的jvm上
                                                        //后续的操作都是通过JNIEnv实现的
        JLI_ReportErrorMessage(JVM_ERROR1);
        exit(1);
    }

    if (showSettings != NULL) {
        ShowSettings(env, showSettings);
        CHECK_EXCEPTION_LEAVE(1);
    }

    if (printVersion || showVersion) {
        PrintJavaVersion(env, showVersion);
        CHECK_EXCEPTION_LEAVE(0);
        if (printVersion) {
            LEAVE();
        }
    }

    /* 如果没有指定class或者jar,则直接退出 */    if (printXUsage || printUsage || what == 0 || mode == LM_UNKNOWN) {
        PrintUsage(env, printXUsage);
        CHECK_EXCEPTION_LEAVE(1);
        LEAVE();
    }

    FreeKnownVMs();  /* after last possible PrintUsage() */
    /* 记录初始化jvm时间 */    if (JLI_IsTraceLauncher()) {
        end = CounterGet();
        JLI_TraceLauncher("%ld micro seconds to InitializeJVM\n",
               (long)(jint)Counter2Micros(end-start));
    }

    /* 接下来,从argv获取应用的参数,打印参数 */    if (JLI_IsTraceLauncher()){
        int i;
        printf("%s is '%s'\n", launchModeNames[mode], what);
        printf("App's argc is %d\n", argc);
        for (i=0; 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).
     *
     * This method also correctly handles launching existing JavaFX
     * applications that may or may not have a Main-Class manifest entry.
     */
    /* 加载Main class */    mainClass = LoadMainClass(env, mode, what);
    /* 检查是否指定main class, 不存在则退出虚拟机*/    CHECK_EXCEPTION_NULL_LEAVE(mainClass);
    /*
     * In some cases when launching an application that needs a helper, e.g., a
     * JavaFX application with no main method, the mainClass will not be the
     * applications own main class but rather a helper class. To keep things
     * consistent in the UI we need to track and report the application main class.
     */    /* 获取应用的class文件 */    appClass = GetApplicationClass(env);
    /* 检查是否指定app class, 不存在返回-1 */    NULL_CHECK_RETURN_VALUE(appClass, -1);
    /*
     * PostJVMInit uses the class name as the application name for GUI purposes,
     * for example, on OSX this sets the application name in the menu bar for
     * both SWT and JavaFX. So we'll pass the actual application class here
     * instead of mainClass as that may be a launcher or helper class instead
     * of the application class.
     */    /*window和类unix为空实现,在macos下设定gui程序的程序名称*/    PostJVMInit(env, appClass, vm);
    /*
     * The LoadMainClass not only loads the main class, it will also ensure
     * that the main method's signature is correct, therefore further checking
     * is not required. The main method is invoked here so that extraneous java
     * stacks are not in the application stack trace.
     */    /*从mainclass中加载静态main方法*/    mainID = (*env)->GetStaticMethodID(env, mainClass, "main",
                                       "([Ljava/lang/String;)V");
    CHECK_EXCEPTION_NULL_LEAVE(mainID);

    /* Build platform specific argument array */    mainArgs = CreateApplicationArgs(env, argv, argc);
    CHECK_EXCEPTION_NULL_LEAVE(mainArgs);

    /* Invoke main method. */    /* 调用main方法,并把参数传递过去 */    (*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;
    LEAVE();
}  

InitializeJVM

作用:通过libvm.so导出的函数创建虚拟机并和JNIEnv绑定

位置:jdk8u-dev/jdk/src/share/bin/java.c

 /*
 * 初始化java虚拟机
 */static jboolean
InitializeJVM(JavaVM **pvm,               //虚拟机指针
              JNIEnv **penv,              //JNIEnv指针
              InvocationFunctions *ifn)   //导出的函数指针
{
    JavaVMInitArgs args;
    jint r;

    memset(&args, 0, sizeof(args));
    args.version  = JNI_VERSION_1_2;      //指定jni版本号
    args.nOptions = numOptions;
    args.options  = options;
    args.ignoreUnrecognized = JNI_FALSE;

    if (JLI_IsTraceLauncher()) {          //打印参数
        int i = 0;
        printf("JavaVM args:\n    ");
        printf("version 0x%08lx, ", (long)args.version);
        printf("ignoreUnrecognized is %s, ",
               args.ignoreUnrecognized ? "JNI_TRUE" : "JNI_FALSE");
        printf("nOptions is %ld\n", (long)args.nOptions);
        for (i = 0; i < numOptions; i++)
            printf("    option[%2d] = '%s'\n",
                   i, args.options[i].optionString);
    }

    /* 创建java虚拟机,并和JNIEnv绑定 */    r = ifn->CreateJavaVM(pvm, (void **)penv, &args);
    /* 释放参数内存 */    JLI_MemFree(options);
    /* 返回创建结果 */    return r == JNI_OK;
}
  

JNI_CreateJavaVM

作用:创建java虚拟机
位置:jdk8u-dev/hotspot/src/share/vm/prims/jni.cpp

 /* 导出JNI_CreateJavaVM函数 */_JNI_IMPORT_OR_EXPORT_ jint JNICALL JNI_CreateJavaVM(JavaVM **vm, void **penv, void *args) {
#ifndef USDT2
  HS_DTRACE_PROBE3(hotspot_jni, CreateJavaVM__entry, vm, penv, args);
#else /* USDT2 */  HOTSPOT_JNI_CREATEJAVAVM_ENTRY(
                                 (void **) vm, penv, args);
#endif /* USDT2 */
  jint result = JNI_ERR;
  DT_RETURN_MARK(CreateJavaVM, jint, (const jint&)result);

  // We're about to use Atomic::xchg for synchronization.  Some Zero
  // platforms use the GCC builtin __sync_lock_test_and_set for this,
  // but __sync_lock_test_and_set is not guaranteed to do what we want
  // on all architectures.  So we check it works before relying on it.
#if defined(ZERO) && defined(ASSERT)
  {
    jint a = 0xcafebabe;
    jint b = Atomic::xchg(0xdeadbeef, &a);
    void *c = &a
    void *d = Atomic::xchg_ptr(&b, &c);
    assert(a == (jint) 0xdeadbeef && b == (jint) 0xcafebabe, "Atomic::xchg() works");
    assert(c == &b && d == &a, "Atomic::xchg_ptr() works");
  }
#endif // ZERO && ASSERT

  // At the moment it's only possible to have one Java VM,
  // since some of the runtime state is in global variables.

  // We cannot use our mutex locks here, since they only work on
  // Threads. We do an atomic compare and exchange to ensure only
  // one thread can call this method at a time

  // We use Atomic::xchg rather than Atomic::add/dec since on some platforms
  // the add/dec implementations are dependent on whether we are running
  // on a multiprocessor, and at this stage of initialization the os::is_MP
  // function used to determine this will always return false. Atomic::xchg
  // does not have this problem.
  if (Atomic::xchg(1, &vm_created) == 1) {
    return JNI_EEXIST;   // already created, or create attempt in progress
  }
  if (Atomic::xchg(0, &safe_to_recreate_vm) == 0) {
    return JNI_ERR;  // someone tried and failed and retry not allowed.
  }

  assert(vm_created == 1, "vm_created is true during the creation");

  /**
  * 初始化期间的某些错误是可恢复的,并且不会阻止稍后再次调用此方法(可能使用不同的参数)。 
  * 但是,在初始化期间的某个时刻如果发生错误,我们不能再次调用此函数(否则它将崩溃)。 
  * 在这些情况下,ccan_try_again标志设置为false,它将 safe_to_recreate_vm 原子地设置为1,
  * 这样任何对JNI_CreateJavaVM的新调用都将立即使用上述逻辑失败。
  */  bool can_try_again = true;

  /* 创建java虚拟机 */  result = Threads::create_vm((JavaVMInitArgs*) args, &can_try_again);
  if (result == JNI_OK) {
    JavaThread *thread = JavaThread::current();
    /* thread is thread_in_vm here */    /* 获取vm对象,从全局的main_vm赋值 */    *vm = (JavaVM *)(&main_vm);
    /* 获取线程jni环境指针 */    *(JNIEnv**) penv = thread->jni_environment();

    // Tracks the time application was running before GC
    // 记录程序启动时间,跟从应用程序在gc前的运行时间
    RuntimeService::record_application_start();

    // Notify JVMTI
    // 
    if (JvmtiExport::should_post_thread_life()) {
       JvmtiExport::post_thread_start(thread);
    }

    EventThreadStart event;
    if (event.should_commit()) {
      event.set_javalangthread(java_lang_Thread::thread_id(thread->threadObj()));
      event.commit();
    }

#ifndef PRODUCT
  #ifndef TARGET_OS_FAMILY_windows
    #define CALL_TEST_FUNC_WITH_WRAPPER_IF_NEEDED(f) f()
  #endif

    // Check if we should compile all classes on bootclasspath
    if (CompileTheWorld) ClassLoader::compile_the_world();
    if (ReplayCompiles) ciReplay::replay(thread);

    // Some platforms (like Win*) need a wrapper around these test
    // functions in order to properly handle error conditions.
    CALL_TEST_FUNC_WITH_WRAPPER_IF_NEEDED(test_error_handler);
    CALL_TEST_FUNC_WITH_WRAPPER_IF_NEEDED(execute_internal_vm_tests);
#endif

    // Since this is not a JVM_ENTRY we have to set the thread state manually before leaving.
    ThreadStateTransition::transition_and_fence(thread, _thread_in_vm, _thread_in_native);
  } else {
    /* 创建失败,如果允许重试,则修改safe_to_recreate_vm标志 */    if (can_try_again) {
      // reset safe_to_recreate_vm to 1 so that retrial would be possible
      safe_to_recreate_vm = 1;
    }

    // Creation failed. We must reset vm_created
    // 创建失败,需要重新设置vm_create值
    *vm = 0;
    *(JNIEnv**)penv = 0;
    // reset vm_created last to avoid race condition. Use OrderAccess to
    // control both compiler and architectural-based reordering.
    OrderAccess::release_store(&vm_created, 0);
  }

  return result;
}  

Threads::create_vm

作用:hotspot创建java虚拟机函数
位置:jdk8u-dev/hotspot/src/share/vm/runtime/thread.cpp

 jint Threads::create_vm(JavaVMInitArgs* args, bool* canTryAgain) {
  //调用JDK_Version_init();
  //加载libjava.so、libverify.so库,通过调用库中导出的JDK_GetVersionInfo0查询当前虚拟机版本
  extern void JDK_Version_init();

  // 检查当前版本是否在支持范围内,主要不支持1.1版本 高于1.1都返回true
  if (!is_supported_jni_version(args->version)) return JNI_EVERSION;

  //初始化系统输出流模块
  ostream_init();

  // 处理java启动属性.
  Arguments::process_sun_java_launcher_properties(args);

  // Initialize the os module before using TLS
  // 初始化系统环境,例如:获取当前的进程pid、获取系统时钟、设置内存页大小
  // 获取cpu数、获取物理内存大小
  os::init();

  // Initialize system properties.
  // 设置系统属性, key-value
  // 设置了java虚拟机信息、清空由os::init_system_properties_values()方法设置的值
  Arguments::init_system_properties();

  // So that JDK version can be used as a discrimintor when parsing arguments
  // 获取jdk版本号,作为接下来参数解析的依据
  JDK_Version_init();

  // Update/Initialize System properties after JDK version number is known
  // 设定jdk版本后再去更新系统版本、供应商属性值,此处主要设定3个参数
  // jdk1.7以后的版本厂商修改为oracle,之前为sun
  // 1、java.vm.specification.vendor     可选值为:Oracle Corporation / Sun Microsystems Inc.
  // 2、java.vm.specification.version    可选值为:1.0  /  1.7  1.8  1.9 etc
  // 3、java.vm.vendor                   可选值为: Oracle Corporation / Sun Microsystems Inc.
  Arguments::init_version_specific_system_properties();

  // Parse arguments
  // 解析参数,生成java虚拟机运行期间的一些参数指标
  // -XX:Flags=   -XX:+PrintVMOptions  -XX:-PrintVMOptions    -XX:+IgnoreUnrecognizedVMOptions
  // -XX:-IgnoreUnrecognizedVMOptions  -XX:+PrintFlagsInitial -XX:NativeMemoryTracking
  jint parse_result = Arguments::parse(args);
  if (parse_result != JNI_OK) return parse_result;

  //主要完成大页支持以及linux上的coredump_filter配置
  os::init_before_ergo();

  //调整运行环境得参数及一些指标
  //1、设置参数及指标  如果是server模式并且没有指定gc策略,则默认使用UsePaallelGC
  // 64位下启用普通对象压缩
  //2、设置是否使用共享存储空间(开启对象压缩、指针压缩后关闭)
  //3、检查gc一致性
  //4、依据物理内存设置对内存大小
  //5、设置各种gc的参数标志:parallel、cms、g1、panew
  //6、初始化jvm的平衡因子参数
  //7、设定字节码重写标志
  //8、如果指定-XX:+AggressiveOpts参数,则设定加快编译,依赖于EliminateAutoBox及DoEscapeAnalysis标志位及C2下
  //9、根据是否指定gama lanucher及是否是调试状态设定暂停状态位
  jint ergo_result = Arguments::apply_ergo();
  if (ergo_result != JNI_OK) return ergo_result;

  //暂停
  if (PauseAtStartup) {
    os::pause();
  }

#ifndef USDT2
  HS_DTRACE_PROBE(hotspot, vm__init__begin);
#else /* USDT2 */  HOTSPOT_VM_INIT_BEGIN();
#endif /* USDT2 */
  // Record VM creation timing statistics
  //记录jvm启动开始时间
  TraceVmCreationTime create_vm_timer;
  create_vm_timer.start();

  // Timing (must come after argument parsing)
  TraceTime timer("Create VM", TraceStartupTime);

  // Initialize the os module after parsing the args
  //解析参数后,初始化系统模块
  //1、如果使用linux下的posix线程库cpu时钟的话,使用pthread_getcpuclockid方法获取jvm线程
  //   的cpu时钟id,然后基于这个线程的时钟进行时间统计计数
  //2、分配一个linux内存单页并标记位为可循环读的安全入口点
  //3、初始化暂停/恢复运行的支持,主要通过注册信号处理程序支持该功能
  //4、注册信号处理程序
  //5、安装信号处理程序
  //6、计算线程栈大小
  //7、设置glibc、linux线程版本信息
  //8、设置linux系统进程文件描述符值
  //9、创建用于线程创建的线程锁
  //11、初始化线程的优先级
  jint os_init_2_result = os::init_2();
  if (os_init_2_result != JNI_OK) return os_init_2_result;

  //调整参数,主要针对numa架构下ParallelGC、OarallelOldGc调整堆内存参数
  jint adjust_after_os_result = Arguments::adjust_after_os();
  if (adjust_after_os_result != JNI_OK) return adjust_after_os_result;

  // intialize TLS
  // 初始化线程本地存储,通过ptread_create_key创建一个线程特有的key_t,
  // 后面通过pthread_setspecific\pthread_getspecific设置、获取线程私有数据
  ThreadLocalStorage::init();

  // Bootstrap native memory tracking, so it can start recording memory
  // activities before worker thread is started. This is the first phase
  // of bootstrapping, VM is currently running in single-thread mode.
  // 启动本地内存跟踪,因此它可以在工作线程启动之前开始记录内存活动。 
  // 这是引导的第一阶段,JVM当前以单线程模式运行。
  MemTracker::bootstrap_single_thread();

  // Initialize output stream logging
  // 初始化jvm的日志输出流,如果指定-Xloggc:logfilepath则根据参数指定生成输出流到文件
  ostream_init_log();

  // Convert -Xrun to -agentlib: if there is no JVM_OnLoad
  // Must be before create_vm_init_agents()
  // 如果指定了-Xrun参数,如:hprof性能分析、jdwp远程调试
  if (Arguments::init_libraries_at_startup()) {
    convert_vm_init_libraries_to_agents();
  }

  // Launch -agentlib/-agentpath and converted -Xrun agents
  if (Arguments::init_agents_at_startup()) {
    create_vm_init_agents();
  }

  // Initialize Threads state
  _thread_list = NULL;
  _number_of_threads = 0;
  _number_of_non_daemon_threads = 0;

  // Initialize global data structures and create system classes in heap
  // 1、初始化全局结构体
  // 2、在内存中创建jvm体系的class文件
  // 3、初始化事件记录日志对象
  // 4、初始化全局资源锁mutex
  // 5、内存池初始化large_pool、medium_pool、small_poll、tiny_pool
  // 6、启用该perfdata功能。此选项默认启用以允许JVM监视和性能测试,如果指定 -XX:+UsePerfData
  vm_init_globals();

  // Attach the main thread to this os thread
  // 创建新的主java线程, 设置主线程状态为运行在jvm里面
  JavaThread* main_thread = new JavaThread();
  main_thread->set_thread_state(_thread_in_vm);
  // must do this before set_active_handles and initialize_thread_local_storage
  // Note: on solaris initialize_thread_local_storage() will (indirectly)
  // change the stack size recorded here to one based on the java thread
  // stacksize. This adjusted size is what is used to figure the placement
  // of the guard pages.
  // 记录栈基址、栈大小值
  main_thread->record_stack_base_and_size();
  // 初始化主线程的本地存储
  main_thread->initialize_thread_local_storage();

  // 绑定jniHandleBlock指针, 处理jni逻辑句柄
  main_thread->set_active_handles(JNIHandleBlock::allocate_block());

  // 设定main_thread为主线程
  if (!main_thread->set_as_starting_thread()) {
    vm_shutdown_during_initialization(
      "Failed necessary internal allocation. Out of swap space");
    delete main_thread;
    *canTryAgain = false; // don't let caller call JNI_CreateJavaVM again
    return JNI_ENOMEM;
  }

  // Enable guard page *after* os::create_main_thread(), otherwise it would
  // crash Linux VM, see notes in os_linux.cpp.
  main_thread->create_stack_guard_pages();

  // Initialize Java-Level synchronization subsystem
  ObjectMonitor::Initialize() ;

  // Second phase of bootstrapping, VM is about entering multi-thread mode
  MemTracker::bootstrap_multi_thread();

  // Initialize global modules
  // 初始化全局模块
  // 
  jint status = init_globals();
  if (status != JNI_OK) {
    delete main_thread;
    *canTryAgain = false; // don't let caller call JNI_CreateJavaVM again
    return status;
  }

  // Should be done after the heap is fully created
  main_thread->cache_global_variables();

  HandleMark hm;

  { MutexLocker mu(Threads_lock);
    Threads::add(main_thread);
  }

  // Any JVMTI raw monitors entered in onload will transition into
  // real raw monitor. VM is setup enough here for raw monitor enter.
  JvmtiExport::transition_pending_onload_raw_monitors();

  // Fully start NMT
  MemTracker::start();

  // Create the VMThread
  { TraceTime timer("Start VMThread", TraceStartupTime);
    VMThread::create();
    Thread* vmthread = VMThread::vm_thread();

    if (!os::create_thread(vmthread, os::vm_thread))
      vm_exit_during_initialization("Cannot create VM thread. Out of system resources.");

    // Wait for the VM thread to become ready, and VMThread::run to initialize
    // Monitors can have spurious returns, must always check another state flag
    {
      MutexLocker ml(Notify_lock);
      os::start_thread(vmthread);
      while (vmthread->active_handles() == NULL) {
        Notify_lock->wait();
      }
    }
  }

  assert (Universe::is_fully_initialized(), "not initialized");
  if (VerifyDuringStartup) {
    // Make sure we're starting with a clean slate.
    VM_Verify verify_op;
    VMThread::execute(&verify_op);
  }

  EXCEPTION_MARK;

  // At this point, the Universe is initialized, but we have not executed
  // any byte code.  Now is a good time (the only time) to dump out the
  // internal state of the JVM for sharing.
  if (DumpSharedSpaces) {
    MetaspaceShared::preload_and_dump(CHECK_0);
    ShouldNotReachHere();
  }

  // Always call even when there are not JVMTI environments yet, since environments
  // may be attached late and JVMTI must track phases of VM execution
  JvmtiExport::enter_start_phase();

  // Notify JVMTI agents that VM has started (JNI is up) - nop if no agents.
  JvmtiExport::post_vm_start();

  {
    TraceTime timer("Initialize java.lang classes", TraceStartupTime);

    if (EagerXrunInit && Arguments::init_libraries_at_startup()) {
      create_vm_init_libraries();
    }

    initialize_class(vmSymbols::java_lang_String(), CHECK_0);

    // Initialize java_lang.System (needed before creating the thread)
    initialize_class(vmSymbols::java_lang_System(), CHECK_0);
    initialize_class(vmSymbols::java_lang_ThreadGroup(), CHECK_0);
    Handle thread_group = create_initial_thread_group(CHECK_0);
    Universe::set_main_thread_group(thread_group());
    initialize_class(vmSymbols::java_lang_Thread(), CHECK_0);
    oop thread_object = create_initial_thread(thread_group, main_thread, CHECK_0);
    main_thread->set_threadObj(thread_object);
    // Set thread status to running since main thread has
    // been started and running.
    java_lang_Thread::set_thread_status(thread_object,
                                        java_lang_Thread::RUNNABLE);

    // The VM creates & returns objects of this class. Make sure it's initialized.
    initialize_class(vmSymbols::java_lang_Class(), CHECK_0);

    // The VM preresolves methods to these classes. Make sure that they get initialized
    initialize_class(vmSymbols::java_lang_reflect_Method(), CHECK_0);
    initialize_class(vmSymbols::java_lang_ref_Finalizer(),  CHECK_0);
    call_initializeSystemClass(CHECK_0);

    // get the Java runtime name after java.lang.System is initialized
    JDK_Version::set_runtime_name(get_java_runtime_name(THREAD));
    JDK_Version::set_runtime_version(get_java_runtime_version(THREAD));

    // an instance of OutOfMemory exception has been allocated earlier
    initialize_class(vmSymbols::java_lang_OutOfMemoryError(), CHECK_0);
    initialize_class(vmSymbols::java_lang_NullPointerException(), CHECK_0);
    initialize_class(vmSymbols::java_lang_ClassCastException(), CHECK_0);
    initialize_class(vmSymbols::java_lang_ArrayStoreException(), CHECK_0);
    initialize_class(vmSymbols::java_lang_ArithmeticException(), CHECK_0);
    initialize_class(vmSymbols::java_lang_StackOverflowError(), CHECK_0);
    initialize_class(vmSymbols::java_lang_IllegalMonitorStateException(), CHECK_0);
    initialize_class(vmSymbols::java_lang_IllegalArgumentException(), CHECK_0);
  }

  // See        : bugid 4211085.
  // Background : the static initializer of java.lang.Compiler tries to read
  //              property"java.compiler" and read & write property "java.vm.info".
  //              When a security manager is installed through the command line
  //              option "-Djava.security.manager", the above properties are not
  //              readable and the static initializer for java.lang.Compiler fails
  //              resulting in a NoClassDefFoundError.  This can happen in any
  //              user code which calls methods in java.lang.Compiler.
  // Hack :       the hack is to pre-load and initialize this class, so that only
  //              system domains are on the stack when the properties are read.
  //              Currently even the AWT code has calls to methods in java.lang.Compiler.
  //              On the classic VM, java.lang.Compiler is loaded very early to load the JIT.
  // Future Fix : the best fix is to grant everyone permissions to read "java.compiler" and
  //              read and write"java.vm.info" in the default policy file. See bugid 4211383
  //              Once that is done, we should remove this hack.
  initialize_class(vmSymbols::java_lang_Compiler(), CHECK_0);

  // More hackery - the static initializer of java.lang.Compiler adds the string "nojit" to
  // the java.vm.info property if no jit gets loaded through java.lang.Compiler (the hotspot
  // compiler does not get loaded through java.lang.Compiler).  "java -version" with the
  // hotspot vm says "nojit" all the time which is confusing.  So, we reset it here.
  // This should also be taken out as soon as 4211383 gets fixed.
  reset_vm_info_property(CHECK_0);

  quicken_jni_functions();

  // Must be run after init_ft which initializes ft_enabled
  if (TRACE_INITIALIZE() != JNI_OK) {
    vm_exit_during_initialization("Failed to initialize tracing backend");
  }

  // Set flag that basic initialization has completed. Used by exceptions and various
  // debug stuff, that does not work until all basic classes have been initialized.
  set_init_completed();

#ifndef USDT2
  HS_DTRACE_PROBE(hotspot, vm__init__end);
#else /* USDT2 */  HOTSPOT_VM_INIT_END();
#endif /* USDT2 */
  // record VM initialization completion time
#if INCLUDE_MANAGEMENT
  Management::record_vm_init_completed();
#endif // INCLUDE_MANAGEMENT

  // Compute system loader. Note that this has to occur after set_init_completed, since
  // valid exceptions may be thrown in the process.
  // Note that we do not use CHECK_0 here since we are inside an EXCEPTION_MARK and
  // set_init_completed has just been called, causing exceptions not to be shortcut
  // anymore. We call vm_exit_during_initialization directly instead.
  SystemDictionary::compute_java_system_loader(THREAD);
  if (HAS_PENDING_EXCEPTION) {
    vm_exit_during_initialization(Handle(THREAD, PENDING_EXCEPTION));
  }

#if INCLUDE_ALL_GCS
  // Support for ConcurrentMarkSweep. This should be cleaned up
  // and better encapsulated. The ugly nested if test would go away
  // once things are properly refactored. XXX YSR
  if (UseConcMarkSweepGC || UseG1GC) {
    if (UseConcMarkSweepGC) {
      ConcurrentMarkSweepThread::makeSurrogateLockerThread(THREAD);
    } else {
      ConcurrentMarkThread::makeSurrogateLockerThread(THREAD);
    }
    if (HAS_PENDING_EXCEPTION) {
      vm_exit_during_initialization(Handle(THREAD, PENDING_EXCEPTION));
    }
  }
#endif // INCLUDE_ALL_GCS

  // Always call even when there are not JVMTI environments yet, since environments
  // may be attached late and JVMTI must track phases of VM execution
  JvmtiExport::enter_live_phase();

  // Signal Dispatcher needs to be started before VMInit event is posted
  os::signal_init();

  // Start Attach Listener if +StartAttachListener or it can't be started lazily
  if (!DisableAttachMechanism) {
    AttachListener::vm_start();
    if (StartAttachListener || AttachListener::init_at_startup()) {
      AttachListener::init();
    }
  }

  // Launch -Xrun agents
  // Must be done in the JVMTI live phase so that for backward compatibility the JDWP
  // back-end can launch with -Xdebug -Xrunjdwp.
  if (!EagerXrunInit && Arguments::init_libraries_at_startup()) {
    create_vm_init_libraries();
  }

  // Notify JVMTI agents that VM initialization is complete - nop if no agents.
  JvmtiExport::post_vm_initialized();

  if (TRACE_START() != JNI_OK) {
    vm_exit_during_initialization("Failed to start tracing backend.");
  }

  if (CleanChunkPoolAsync) {
    Chunk::start_chunk_pool_cleaner_task();
  }

  // initialize compiler(s)
#if defined(COMPILER1) || defined(COMPILER2) || defined(SHARK)
  CompileBroker::compilation_init();
#endif

  if (EnableInvokeDynamic) {
    // Pre-initialize some JSR292 core classes to avoid deadlock during class loading.
    // It is done after compilers are initialized, because otherwise compilations of
    // signature polymorphic MH intrinsics can be missed
    // (see SystemDictionary::find_method_handle_intrinsic).
    initialize_class(vmSymbols::java_lang_invoke_MethodHandle(), CHECK_0);
    initialize_class(vmSymbols::java_lang_invoke_MemberName(), CHECK_0);
    initialize_class(vmSymbols::java_lang_invoke_MethodHandleNatives(), CHECK_0);
  }

#if INCLUDE_MANAGEMENT
  Management::initialize(THREAD);
#endif // INCLUDE_MANAGEMENT

  if (HAS_PENDING_EXCEPTION) {
    // management agent fails to start possibly due to
    // configuration problem and is responsible for printing
    // stack trace if appropriate. Simply exit VM.
    vm_exit(1);
  }

  if (Arguments::has_profile())       FlatProfiler::engage(main_thread, true);
  if (MemProfiling)                   MemProfiler::engage();
  StatSampler::engage();
  if (CheckJNICalls)                  JniPeriodicChecker::engage();

  BiasedLocking::init();

  if (JDK_Version::current().post_vm_init_hook_enabled()) {
    call_postVMInitHook(THREAD);
    // The Java side of PostVMInitHook.run must deal with all
    // exceptions and provide means of diagnosis.
    if (HAS_PENDING_EXCEPTION) {
      CLEAR_PENDING_EXCEPTION;
    }
  }

  {
      MutexLockerEx ml(PeriodicTask_lock, Mutex::_no_safepoint_check_flag);
      // Make sure the watcher thread can be started by WatcherThread::start()
      // or by dynamic enrollment.
      WatcherThread::make_startable();
      // Start up the WatcherThread if there are any periodic tasks
      // NOTE:  All PeriodicTasks should be registered by now. If they
      //   aren't, late joiners might appear to start slowly (we might
      //   take a while to process their first tick).
      if (PeriodicTask::num_tasks() > 0) {
          WatcherThread::start();
      }
  }

  // Give os specific code one last chance to start
  os::init_3();

  create_vm_timer.end();
#ifdef ASSERT
  _vm_complete = true;
#endif
  return JNI_OK;
}  

文章来源:智云一二三科技

文章标题:OpenJdk1.8笔记——java启动流程

文章地址:https://www.zhihuclub.com/188459.shtml

关于作者: 智云科技

热门文章

网站地图