jvm源码分析之启动过程

1 篇文章 0 订阅

这里介绍的是jdk8u的源码,系统环境是Linux version 4.15.0-142-generic (buildd@lgw01-amd64-039) (gcc version 5.4.0 20160609 (Ubuntu 5.4.0-6ubuntu1~16.04.12)) #146~16.04.1-Ubuntu SMP Tue Apr 13 09:27:15 UTC 2021

启动入口函数

jvm启动的入口函数在jdk/src/share/bin目录下main.c文件中,我们这里是64位操作系统,调试的文件是普通带有main方法的Java的class文件,main.c在中的主方法

int main(int argc, char **argv)
{  
    int margc;
    char** margv;
    const jboolean const_javaw = JNI_FALSE;
    #endif /* JAVAW */
    #ifdef _WIN32 //这里是64位系统直接跳过到创建JLI_Launch
    {
        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 */
    return JLI_Launch(margc, margv,
        sizeof(const_jargs) / sizeof(char *), const_jargs,
        sizeof(const_appclasspath) / sizeof(char *), const_appclasspath,
        FULL_VERSION,
        DOT_VERSION,
        (const_progname != NULL) ? const_progname : *margv,
        (const_launcher != NULL) ? const_launcher : *margv,
        (const_jargs != NULL) ? JNI_TRUE : JNI_FALSE,
        const_cpwildcard, const_javaw, const_ergo_class);
}

调试主要执行了一个赋值和创建引导器JLI_Launch,这里的参数个数是4个,分别是编译好的java可执行文件,cp类路径,编译好的class文件

加载libjvm.so动态库文件过程

继续debug查看库文件的加载过程.java.c中的启动方法

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;
    char *what = NULL;
    char *cpath = 0;
    char *main_class = NULL;
    int ret;
    InvocationFunctions ifn;
    jlong start = 0, end = 0;
    char jvmpath[MAXPATHLEN];
    char jrepath[MAXPATHLEN];
    char jvmcfg[MAXPATHLEN];

    _fVersion = fullversion;//1.8.0_352-internal-debug-root_2023_03_29_04_00-b00
    _dVersion = dotversion;//1.8
    _launcher_name = lname;//openjdk
    _program_name = pname;//java
    _is_java_args = javaargs;//0
    _wc_enabled = cpwildcard;//1
    _ergo_policy = ergo;//0
	//初始化引导器,这里javaw是0,没有main函数的应用创建启动类引导器
    InitLauncher(javaw);
    DumpState();//打印参数信息
    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);
    }

    /*
     * 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);//根据版本设置相应参数

    /** **/
    CreateExecutionEnvironment(&argc, &argv,
                               jrepath, sizeof(jrepath),
                               jvmpath, sizeof(jvmpath),
                               jvmcfg,  sizeof(jvmcfg));

    if (!IsJavaArgs()) {
        SetJvmEnvironment(argc,argv);//设置jvm的环境变量,比如一些jvm调优参数
    }

    ifn.CreateJavaVM = 0;
    ifn.GetDefaultJavaVMInitArgs = 0;

    if (JLI_IsTraceLauncher()) {
        start = CounterGet();
    }
	/*这里通过调用系统函数dlopen(jvmpath, RTLD_NOW + RTLD_GLOBAL)加载文件libjvm.so,
	* 通过 void *dlsym(void *handle, const char *symbol)拿到handle函数的地址(函数指针)
    * ifn->CreateJavaVM = (CreateJavaVM_t) dlsym(libjvm, "JNI_CreateJavaVM");
 	* 这里得jvmpath就是openjdk源码编译生成的库文件地址,在源码编译后的build文件夹中
	*/
    if (!LoadJavaVM(jvmpath, &ifn)) {
        return(6);
    }

    if (JLI_IsTraceLauncher()) {
        end   = CounterGet();
    }

    JLI_TraceLauncher("%ld micro seconds to LoadJavaVM\n",
             (long)(jint)Counter2Micros(end-start));

    ++argv;
    --argc;

    if (IsJavaArgs()) {
        /* Preprocess wrapper arguments */
        TranslateApplicationArgs(jargc, jargv, &argc, &argv);
        if (!AddApplicationOptions(appclassc, appclassv)) {
            return(1);
        }
    } else {
        /* Set default CLASSPATH */
        cpath = getenv("CLASSPATH");
        if (cpath == NULL) {
            cpath = ".";
        }
        SetClassPath(cpath);
    }

    /* Parse command line options; if the return value of
     * ParseArguments is false, the program should exit.
     */
    if (!ParseArguments(&argc, &argv, &mode, &what, &ret, jrepath))
    {
        return(ret);
    }

    /* Override class path if -jar flag was specified */
    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();
	/** 这里继续调用java_md_slinux.c文件 JVMInit方法-》Java.c的ContinueInNewThread,
     *	最后还是调用java_md_slinux.c文件的ContinueInNewThread0方法,
     *  在这里创建java线程,并继续在新创建的Java线程里执行javaMain方法
	 */
    return JVMInit(&ifn, threadStackSize, argc, argv, mode, what, ret);
}

主要拿到的函数是JNI_CreateJavaVM,JNI_GetDefaultJavaVMInitArgs,JNI_GetCreatedJavaVMs,拿到动态库中的这三个函数后继续jvm的初始化,接下来就是参数的解析赋值,创建vm线程,编译线程等,java_md_solinux.c

int
ContinueInNewThread0(int (JNICALL *continuation)(void *), jlong stack_size, void * args) {
    int rslt;
#ifndef __solaris__
    pthread_t tid;
    pthread_attr_t attr;
    pthread_attr_init(&attr);
    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);

    if (stack_size > 0) {
      pthread_attr_setstacksize(&attr, stack_size);
    }
	/**在这里系统调用创建线程函数,在新的线程中继续执行函数,continuation就是JavaMain函数**/
    if (pthread_create(&tid, &attr, (void *(*)(void*))continuation, (void*)args) == 0) {
      void * tmp;
      pthread_join(tid, &tmp);
      rslt = (int)tmp;
    } else {
     /*
      * 如果由于某些原因(例如内存不足/LWP)无法创建新线程,则继续在当前线程中执行。这很可能会失败
      * 因为JNI_CreateJavaVM需要创建相当多的新线程,无论如何,请尝试一下
      */
      rslt = continuation(args);
    }

    pthread_attr_destroy(&attr);
#else /* __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 /* !__solaris__ */
    return rslt;
}

java虚拟机的创建

JavaMain函数中就开始初始化并且创建jvm相关线程,有VM线程,C1编译线程,C2编译线程,service线程,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;

    JavaVM *vm = 0;
    JNIEnv *env = 0;
    jclass mainClass = NULL;
    jclass appClass = NULL; // actual application class being launched
    jmethodID mainID;
    jobjectArray mainArgs;
    int ret = 0;
    jlong start = 0, end = 0;

    RegisterThread();
    start = CounterGet();
     /* 这里正式初始化创建jvm */
    if (!InitializeJVM(&vm, &env, &ifn)) {
        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();
        }
    }

    /* If the user specified neither a class name nor a JAR file */
    if (printXUsage || printUsage || what == 0 || mode == LM_UNKNOWN) {
        PrintUsage(env, printXUsage);
        CHECK_EXCEPTION_LEAVE(1);
        LEAVE();
    }

    FreeKnownVMs();  /* after last possible PrintUsage() */

    if (JLI_IsTraceLauncher()) {
        end = CounterGet();
        JLI_TraceLauncher("%ld micro seconds to InitializeJVM\n",
               (long)(jint)Counter2Micros(end-start));
    }

    /* At this stage, argc/argv have the application's arguments */
    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;

    /*
	 * 获取应用的主类main class
     *
     * Main-Class名称已经从清单中解析出来了,但是没有正确解析出UTF-8支持。
     * 因此,这里的代码忽略了先前提取的值,并使用先前存在的代码重新提取该值。
	 * 这可能是结束发布周期的权宜之计。然而,也发现通过环境传递一些字符集在某些
	 * Windows变体上有“奇怪”的行为。因此,可能永远不应该增强启动器本地的清单解析代码。
	 *
	 * 因此,接下来的操作是
	 * 1)纠正本地解析代码,并验证Main-Class属性在所有环境中正确传递。
	 * 2)删除通过环境维护main_class的遗留问题(并删除这些注释)。该方法还可以正确地处理启动现有的JavaFX应用程序,这些应用程序可能有也可能没有Main-Class清单条目。
     */
    mainClass = LoadMainClass(env, mode, what);/*  */
    CHECK_EXCEPTION_NULL_LEAVE(mainClass);
    /*
	 * 在某些场景下,当启动一个应用程序的时候需要一个帮助类启动器,例如
     * 没有main方法的JavaFX应用程序,这类应用程序的main-class就是帮助类启动器里面的主类
     * 为了保证在UI上保持一致所以需要追踪和报告应用程序的main-class
     */
    appClass = GetApplicationClass(env);
    NULL_CHECK_RETURN_VALUE(appClass, -1);
    /*
     * PostJVMInit使用类名作为GUI用途的应用程序名称,例如,在OSX上
	 * 它在菜单栏中为SWT和JavaFX设置应用程序名称。我们在这里传递实际
     * 的应用程序类,而不是mainClass,因为那可能是一个启动器或助手类
     * 而不是应用程序类。
     */
    PostJVMInit(env, appClass, vm);
    CHECK_EXCEPTION_LEAVE(1);
    /*
     * LoadMainClass 不仅加载主类,还需要确保主方法的签名是正确的,
     * 因此将不需要进一步检查。这里调用main方法,以便在应用程序堆栈跟踪中
	 * 不存在无关的java堆栈
     */
    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方法 */
    (*env)->CallStaticVoidMethod(env, mainClass, mainID, mainArgs);

    /*
     * 推出引导器System.exit,如果是非正常推出ret != 0
     */
    ret = (*env)->ExceptionOccurred(env) == NULL ? 0 : 1;
    LEAVE();
}

下面我们接着来看InitializeJVM(&vm, &env, &ifn)做了什么,调用libjvm.so动态库中的函数

这个方法的实现是再jni.cpp中,这里会看到xchg这个命令,这个就是java中atomic变量在底层硬件层面的指令实行,这里对jvm的创建同步控制就使用了这个,jni.cpp的主要方法是这个

_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);

  //这里将要使用 Atomic::xchg 命令进行同步,某些 Zero
  // 平台使用 GCC builtin __sync_lock_test_and_set 来实现同步,
  // 但是 __sync_lock_test_and_set 并不保证在所有的架构上执行的结果如我们预期的那样
  // 所以在使用前需要先进行检测
#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

  //因为一些运行时状态在全局变量中
  //所以某一时刻只能有一个Java虚拟机
  //不能使用mutex locks,因为他们只对线程有效
  //使用原子的比较和交换以保证某一时刻只有一个线程调用这个方法
  //这里使用 Atomic::xchg而不是用Atomic::add/dec 
  //因为在某些平台上add/dec的实现依赖我们的程序是否是跑在多核处理器上和初始阶段
  //os::is_MP函数的返回值为false,而 Atomic::xchg则没有这个问题
  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");

  /**初始化期间的某些错误是可以恢复的,并且不会阻止以后再次调用此方法(可能使用不同的参数)
  * 然而,在初始化过程中的某个时刻,如果发生错误,我们不能允许再次调用此函数(否则它将崩溃)
  * 在这些情况下,“canTryAgain”标志被设置为false,这将原子地将safe_to_recreate_vm设置为1,
  * 这样使用上述逻辑对JNI_CreateJavaVM的任何新调用都将立即失败
  */
  bool can_try_again = true;
  /*调用thread.cpp创建jvm虚拟机*/
  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);
    *(JNIEnv**)penv = thread->jni_environment();

    // Tracks the time application was running before GC
    RuntimeService::record_application_start();

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

    post_thread_start_event(thread);

#ifndef PRODUCT
  #ifndef CALL_TEST_FUNC_WITH_WRAPPER_IF_NEEDED
    #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 {
    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 = 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;
}

继续debug查看在这里会去加载class文件,这里的what就是我的测试class文件Test

这就是jvm的启动过程了,下一期再深入到jvm类的加载过程中去看看

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Prometheus Exporter是一种用于将JVM的指标信息暴露给Prometheus监控系统的工具。您提到的源码分析可以参考以下步骤: 1. 首先,您需要下载jmx_exporter,并将其jar包(jmx_prometheus_javaagent-0.16.1.jar)放置在Kafka的家目录下。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [prometheus(jvm_exporter监控kafka jvm)](https://blog.csdn.net/weixin_45837370/article/details/121232020)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT0_1"}}] [.reference_item style="max-width: 33.333333333333336%"] - *2* [jmx_exporter源码分析](https://blog.csdn.net/qqqq0199181/article/details/83792364)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT0_1"}}] [.reference_item style="max-width: 33.333333333333336%"] - *3* [prometheus jmx_exporter 源码分析](https://blog.csdn.net/weixin_40455124/article/details/105693264)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT0_1"}}] [.reference_item style="max-width: 33.333333333333336%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值