深入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虚拟机精讲》

阅读全文
0 0
原创粉丝点击