Hotspot源码解析-第四章

第四章

4.1 线程创建

4.1.1 java.c

4.1.1.1 ContinueInNewThread

threadStackSize参数表示线程执行时的栈空间,因为每个线程执行时都要有自己的私有栈空间做数据存储,所以这是必须的, 这个值可以自己设置,不设置的话,系统会自己默认给个值:

linux64位系统默认是1024k,32位系统默认是320k,这个可以看图4-1

另外,自己查看threadStackSize栈大小可以通过下列方式:

java -XX:+PrintFlagsFinal -version | grep ThreadStackSize

int
ContinueInNewThread(InvocationFunctions* ifn, jlong threadStackSize,
                    int argc, char **argv,
                    int mode, char *what, int ret)
{

    /*
     * 如果用户没有指定 threadStackSize 大小,那就要去检查下VM自带的默认值。Hotspot本身不再支持jdk1.1版本,但可以通过init args结构返回其默认堆栈大小,调用 GetDefaultJavaVMInitArgs(对应的实际函数是JNI_GetDefaultJavaVMInitArgs),这个函数可以取到VM设置的默认的 threadStackSize 大小。这里为什么用不支持jdk1.1但是又用jdk1.1来做参数,我想原因是历史遗留,当然这个也不重要,我们只需要知道这里是可以拿到默认 threadStackSize 大小的就行。
     */
    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;
      }
    }

    { /* 创建一个新的线程去构建JVM并调用主函数 */
      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);
      /* 
       * 如果调用者认为存在错误(ret就是调用者给过来的),我们只需返回错误,否则我们返回被调用者的值
       */
      return (ret != 0) ? ret : rslt;
    }
}

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

4.1.2 java_md_solinux.c

4.1.2.1 ContinueInNewThread0

这个函数就是通过系统调用API创建一个新的线程,执行continuation函数指针指向的函数。

JNICALL这里介绍一下,后续文章中会出现多次JNI开头的类型,比如JNIEnv、JNIHandle等,顾名思义,这些都跟JNI相关,那么怎么理解呢?先看下图
在这里插入图片描述

参考图-1下linux的用户应用的调用流程,图-2是java应用在jdk环境下调用流程,写过java的都知道,java应用调用c/c++的函数是要通过jni来实现的,那么不光是这种情况要用jni,就是java应用底层的c/c++函数调用jvm虚拟机(hotspot)内部的函数实现,也是通过jni来实现的,从上面来看,严格意义来讲,jvm只有黄色标注的那一小块,其他块调用它,都需要jni来实现,所以从这点看,jni的调用存在两个层面,一个是java层面调用c/c++,另一个是c/c++层面调用虚拟机的实现。有了这个前提后面再看见jni就清晰。

int
ContinueInNewThread0(int (JNICALL *continuation)(void *), jlong stack_size, void * args) {
    int rslt;
#ifndef __solaris__   // 这里是ifndef,也就非solaris,这里也就是指Linux实现
    pthread_t tid;  // 线程id
    pthread_attr_t attr; // 线程属性
    // 通过调用系统调用api,初始化线程属性结构,系统调用api的实现细节,可以自己去找手册查看
    pthread_attr_init(&attr);  
    /* 设置线程的分离状态
     * detachstate:
     * 		PTHREAD_CREATE_DETACHED 表示分离
     * 		PTHREAD_CREATE_JOINABLE 表示结合
     * 如果设置成分离状态,表示无需关注新创建的线程执行结果,由它自行完成,并在完成后由操作系统回收资源;
     * 如果设置成非分离状态,表示父线程需要拿到创建的子线程的执行结果
    */
    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);

    if (stack_size > 0) {
      pthread_attr_setstacksize(&attr, stack_size); // 设置线程栈大小
    }
    // 通过调用系统调用api pthread_create 创建一个新的线程,并指定线程创建后需要执行的任务函数是continuation,返回0表示创建成功。continuation是一个函数指针,指向要执行的函数
    if (pthread_create(&tid, &attr, (void *(*)(void*))continuation, (void*)args) == 0) {
      void * tmp;
      pthread_join(tid, &tmp);  // 等待子线程执行结果,想想java中的join方法,意思是一样的
      rslt = (int)tmp;
    } else {
      // 线程创建失败,就在当前线程下执行任务函数
      rslt = continuation(args);
    }
    //  线程属性销毁
    pthread_attr_destroy(&attr);
#else /* __solaris__ */  // 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;
}

4.2 线程执行

章节4.1.1.1中ContinueInNewThread函数调用ContinueInNewThread0函数时,第一个参数传的是JavaMain函数,告知线程创建成功后,要执行的函数。这个函数在java.c源文件中

4.2.1 java.c

4.2.1.1 JavaMain
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;
    // 这个函数在linux和windows实现中都是空白,也就不管了,把它看作成java里的一个钩子函数即可
    RegisterThread();

    /* 初始化虚拟机,具体细节后续章节都会讲 */
    start = CounterGet();
    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 (printXUsage || printUsage || what == 0 || mode == LM_UNKNOWN) {
        PrintUsage(env, printXUsage);
        CHECK_EXCEPTION_LEAVE(1);
        LEAVE();
    }
    // 释放启动虚拟时加载的jvm.cfg中的配置项占用的空间,jvm.cfg的配置加载在章节3.1.1.1的CreateExecutionEnvironment函数中,不过文章中这块读取被我删除了,想了解更多的,可以直接看源码,jvm.cfg这个文件在未来的版本中可能是要被抛弃的
    FreeKnownVMs();  /* after last possible PrintUsage() */
    // 日志打印
    if (JLI_IsTraceLauncher()) {
        end = CounterGet();
        JLI_TraceLauncher("%ld micro seconds to InitializeJVM\n",
               (long)(jint)Counter2Micros(end-start));
    }

    // 日志打印
    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
    mainClass = LoadMainClass(env, mode, what);
    CHECK_EXCEPTION_NULL_LEAVE(mainClass);
    // LoadMainClass函数求出了mainClass,其实appClass跟mainClass是相同的,所以,这一步拿得就是mainClass,后面会讲这个细节
    appClass = GetApplicationClass(env);
    NULL_CHECK_RETURN_VALUE(appClass, -1);
    // 看成钩子函数就行,linux/windows都没做具体实现
    PostJVMInit(env, appClass, vm);
    CHECK_EXCEPTION_LEAVE(1);
    // 取出主类mainClass的main函数地址,并赋值给mainID
    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);

    /* 这里才真正调用 java对应的main方法. */
    (*env)->CallStaticVoidMethod(env, mainClass, mainID, mainArgs);

    /*
     * 如果exit code不为0,那么main执行抛出了异常
     * 至此,整个java应用执行完毕
     */
    ret = (*env)->ExceptionOccurred(env) == NULL ? 0 : 1;
    LEAVE(); // 这是一个宏定义的函数,内部主要做一次资源回收的工作
}

后续将通过抖音视频/直播的形式分享技术,由于前期要做一些准备和规划,预计2024年6月开始,欢迎关注,如有需要或问题咨询,也可直接抖音沟通交流。
在这里插入图片描述

  • 24
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

多栖码农

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值