Hotspot源码解析-第三章

发布时间:2023年12月22日

第三章

3.1 创建执行环境

3.1.1 java.c

3.1.1.1 CreateExecutionEnvironment

这个函数给jvm运行提前创建执行环境,主要做以下几件事情

1、找到执行程序的路径

2、确定执行平台的架构

3、确定执行模式:client/server

void
CreateExecutionEnvironment(int *pargc, char ***pargv,
                           char jrepath[], jint so_jrepath,
                           char jvmpath[], jint so_jvmpath,
                           char jvmcfg[],  jint so_jvmcfg) {
  
    jboolean jvmpathExists;

    // 设置执行程序的路径
    SetExecname(*pargv);

    /* Check data model flags, and exec process, if needed */
    {
        // 得到linux平台下执行的架构:LIBARCH32NAME-32位机器、LIBARCH64NAME-64位机器、LIBARCHNAME-默认
      char *arch        = (char *)GetArch(); /* like sparc or sparcv9 */
      char * jvmtype    = NULL;
      int  argc         = *pargc;
      char **argv       = *pargv;
      int running       = CURRENT_DATA_MODEL; // 表示正在执行的机器位数,这里我们只讲32位的

      int wanted        = running;      /* What data mode is being
                                           asked for? Current model is
                                           fine unless another model
                                           is asked for */
	 // 省略一些非重要代码
    }
}

3.1.2 java_md_solinux.c

3.1.2.1 SetExecname

这个函数就是找出执行程序的路径,也就是java命令所在的路径,主要按4种方式找,下面按查找优先顺序列出

1、/proc/self/exe 链接路径

2、绝对路径

3、相对路径

4、全局搜索

const char*
SetExecname(char **argv)
{
    char* exec_path = NULL;
#if defined(__solaris__)  // 这块是针对solaris操作系统的,不做解析
    {
        Dl_info dlinfo;
        int (*fptr)();

        fptr = (int (*)())dlsym(RTLD_DEFAULT, "main");
        if (fptr == NULL) {
            JLI_ReportErrorMessage(DLL_ERROR3, dlerror());
            return JNI_FALSE;
        }

        if (dladdr((void*)fptr, &dlinfo)) {
            char *resolved = (char*)JLI_MemAlloc(PATH_MAX+1);
            if (resolved != NULL) {
                exec_path = realpath(dlinfo.dli_fname, resolved);
                if (exec_path == NULL) {
                    JLI_MemFree(resolved);
                }
            }
        }
    }
#elif defined(__linux__)  // 这块是针对linux操作系统的,我们就解析这里
    {
        // 在linux操作系统中"/proc/self/exe"链接到当前进程的执行程序目录,比如,当我们在linux操作脚本的时候,这个目录就会链接到bash脚本程序目录,如图3-1,这里是链接到java程序,也就是我们的java命令的目录
        const char* self = "/proc/self/exe";
        char buf[PATH_MAX+1];
        // 把链接的目录读进buf字符数组中
        int len = readlink(self, buf, PATH_MAX);
        if (len >= 0) {
            buf[len] = '\0';            /* readlink(2) doesn't NUL terminate */
            // 将buf数组转成字符串(字符指针指向),供后面逻辑使用
            exec_path = JLI_StringDup(buf);
        }
    }
#else /* !__solaris__ && !__linux__ */
    {
        /* Not implemented */
    }
#endif
    /* 到这一步,还没拿到执行程序路径,那就再通过别的方式去找,具体方式如下:
       1、如果命令是/开头的,那就按绝对路径去找
       2、绝对路径找不到,那再相对路径找
       3、还找不到,就通过系统搜索的方式
    */
    if (exec_path == NULL) {
        exec_path = FindExecName(argv[0]);
    }
    execname = exec_path; // 将得到的执行程序路径存储到全局变量execname中,方便后续逻辑使用
    return exec_path;  // 返回执行程序路径 
}

图3-1
在这里插入图片描述

3.2 设置JVM环境

3.2.1 java.c

3.2.1.1 SetJvmEnvironment

这个函数主要就是设置了NativeMemoryTracking这个参数

static void
SetJvmEnvironment(int argc, char **argv) {

    static const char*  NMT_Env_Name    = "NMT_LEVEL_";
    int i;
    for (i = 0; i < argc; i++) {
        char *arg = argv[i];
        /*
         * 路过一些jvm不需要的参数 (比如 -version or -h), 或者一些应用级参数 (例如 the main class name, or
         * the -jar argument).
         */
        if (i > 0) {
            char *prev = argv[i - 1];
            // skip non-dash arg preceded by class path specifiers
            if (*arg != '-' &&
                    ((JLI_StrCmp(prev, "-cp") == 0
                    || JLI_StrCmp(prev, "-classpath") == 0))) {
                continue;
            }

            if (*arg != '-'
                    || JLI_StrCmp(arg, "-version") == 0
                    || JLI_StrCmp(arg, "-fullversion") == 0
                    || JLI_StrCmp(arg, "-help") == 0
                    || JLI_StrCmp(arg, "-?") == 0
                    || JLI_StrCmp(arg, "-jar") == 0
                    || JLI_StrCmp(arg, "-X") == 0) {
                return;
            }
        }
        /*
         * NativeMemoryTracking 参数是记录jvm向系统申请内存时的埋点,有一定的性能消耗,使用需要谨慎
         */
        if (JLI_StrCCmp(arg, "-XX:NativeMemoryTracking=") == 0) {
            int retval;
            // get what follows this parameter, include "="
            size_t pnlen = JLI_StrLen("-XX:NativeMemoryTracking=");
            if (JLI_StrLen(arg) > pnlen) {
                char* value = arg + pnlen;
                size_t pbuflen = pnlen + JLI_StrLen(value) + 10; // 10 max pid digits

                /*
                 * ensures that malloc successful
                 * DONT JLI_MemFree() pbuf.  JLI_PutEnv() uses system call
                 *   that could store the address.
                 */
                char * pbuf = (char*)JLI_MemAlloc(pbuflen);

                JLI_Snprintf(pbuf, pbuflen, "%s%d=%s", NMT_Env_Name, JLI_GetPid(), value);
                retval = JLI_PutEnv(pbuf);
                if (JLI_IsTraceLauncher()) {
                    char* envName;
                    char* envBuf;

                    // ensures that malloc successful
                    envName = (char*)JLI_MemAlloc(pbuflen);
                    JLI_Snprintf(envName, pbuflen, "%s%d", NMT_Env_Name, JLI_GetPid());

                    printf("TRACER_MARKER: NativeMemoryTracking: env var is %s\n",envName);
                    printf("TRACER_MARKER: NativeMemoryTracking: putenv arg %s\n",pbuf);
                    envBuf = getenv(envName);
                    printf("TRACER_MARKER: NativeMemoryTracking: got value %s\n",envBuf);
                    free(envName);
                }

            }

        }

    }
}

3.3 加载虚拟机

3.3.1 java_md_solinux.c

3.3.1.1 LoadJavaVM

加载Java虚拟机,就是加载动态库libjvm.so,并解析几个用得上的函数CreateJavaVM、GetDefaultJavaVMInitArgs、GetCreatedJavaVMs,这几个函数的用途,后面都会讲

LoadJavaVM(const char *jvmpath, InvocationFunctions *ifn)
{
    void *libjvm;

    JLI_TraceLauncher("JVM path is %s\n", jvmpath);
    /*
    通过dlopen函数打开jvm的动态库libjvm.so,并把它装入内存中,为了后面做符号解析和重定位
    RTLD_NOW:在dlopen返回前,需要解析出所有未定义符号,否则,在dlopen会返回NULL
    RTLD_GLOBAL:动态库中定义的符号可被其后打开的其它库重定位
    */
    libjvm = dlopen(jvmpath, RTLD_NOW + RTLD_GLOBAL);
    if (libjvm == NULL) {
        // 无法通过dlopen打开,那就按正常文件的方式读取,以下都是solaris的实现,本人没有研究过solaris,就不过多描述
#if defined(__solaris__) && defined(__sparc) && !defined(_LP64) /* i.e. 32-bit sparc */
      FILE * fp;
      Elf32_Ehdr elf_head;
      int count;
      int location;
	  // 打开文件
      fp = fopen(jvmpath, "r");
      if (fp == NULL) {
        JLI_ReportErrorMessage(DLL_ERROR2, jvmpath, dlerror());
        return JNI_FALSE;
      }

      /* 读取 elf 文件头*/
      count = fread((void*)(&elf_head), sizeof(Elf32_Ehdr), 1, fp);
      fclose(fp);
      if (count < 1) {
        JLI_ReportErrorMessage(DLL_ERROR2, jvmpath, dlerror());
        return JNI_FALSE;
      }

      /*
       * Check for running a server vm (compiled with -xarch=v8plus)
       * on a stock v8 processor.  In this case, the machine type in
       * the elf header would not be included the architecture list
       * provided by the isalist command, which is turn is gotten from
       * sysinfo.  This case cannot occur on 64-bit hardware and thus
       * does not have to be checked for in binaries with an LP64 data
       * model.
       */
      if (elf_head.e_machine == EM_SPARC32PLUS) {
        char buf[257];  /* recommended buffer size from sysinfo man
                           page */
        long length;
        char* location;

        length = sysinfo(SI_ISALIST, buf, 257);
        if (length > 0) {
            location = JLI_StrStr(buf, "sparcv8plus ");
          if (location == NULL) {
            JLI_ReportErrorMessage(JVM_ERROR3);
            return JNI_FALSE;
          }
        }
      }
#endif
        JLI_ReportErrorMessage(DLL_ERROR1, __LINE__);
        JLI_ReportErrorMessage(DLL_ERROR2, jvmpath, dlerror());
        return JNI_FALSE;
    }
    // dlsym函数对动态库libjvm.so做符号解析,如果库中有函数 JNI_CreateJavaVM 的符号,则把对应符号的函数地址赋值给 CreateJavaVM
    ifn->CreateJavaVM = (CreateJavaVM_t)
        dlsym(libjvm, "JNI_CreateJavaVM");
    if (ifn->CreateJavaVM == NULL) { // 解析失败,就报错并退出函数
        JLI_ReportErrorMessage(DLL_ERROR2, jvmpath, dlerror());
        return JNI_FALSE;
    }
	// dlsym函数对动态库libjvm.so做符号解析,如果库中有函数 JNI_GetDefaultJavaVMInitArgs 的符号,则把对应符号的函数地址赋值给 GetDefaultJavaVMInitArgs
    ifn->GetDefaultJavaVMInitArgs = (GetDefaultJavaVMInitArgs_t)
        dlsym(libjvm, "JNI_GetDefaultJavaVMInitArgs");
    if (ifn->GetDefaultJavaVMInitArgs == NULL) {
        JLI_ReportErrorMessage(DLL_ERROR2, jvmpath, dlerror());
        return JNI_FALSE;
    }
	// dlsym函数对动态库libjvm.so做符号解析,如果库中有函数 JNI_GetCreatedJavaVMs 的符号,则把对应符号的函数地址赋值给 GetCreatedJavaVMs
    ifn->GetCreatedJavaVMs = (GetCreatedJavaVMs_t)
        dlsym(libjvm, "JNI_GetCreatedJavaVMs");
    if (ifn->GetCreatedJavaVMs == NULL) {
        JLI_ReportErrorMessage(DLL_ERROR2, jvmpath, dlerror());
        return JNI_FALSE;
    }

    return JNI_TRUE;
}

3.4 JVM初始化并执行

3.4.1 java_md_solinux.c

3.4.1.1 JVMInit

这个函数才是正式开始执行程序的入口,主要做2件事
1、调用ShowSplashScreen函数展示Java虚拟机的欢迎画面
2、调用ContinueInNewThread函数创建一个新线程并在其中启动Java虚拟机,执行后续流程

int
JVMInit(InvocationFunctions* ifn, jlong threadStackSize,
        int argc, char **argv,
        int mode, char *what, int ret)
{
    // 调用ShowSplashScreen函数展示Java虚拟机的欢迎画面
    ShowSplashScreen();
    // 创建一个新线程并在其中启动Java虚拟机,执行后续流程
    return ContinueInNewThread(ifn, threadStackSize, argc, argv, mode, what, ret);
}

文章来源:https://blog.csdn.net/zhang527294844/article/details/135158364
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。