前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >探秘Java:从main函数启动开始

探秘Java:从main函数启动开始

作者头像
闲宇非鱼
发布2022-02-08 11:39:03
1.3K0
发布2022-02-08 11:39:03
举报

从main函数开始

一、前言

  不知道在座的各位朋友是否跟我一样,初学Java时写下的第一段代码就是类似下面这段代码:

代码语言:javascript
复制
    public static void main(String[] args) {
        System.out.println("hello, world!");
    }

  上面代码的功能非常简单,就是在控制台中打印出"hell, world!"这句话。当然今天我们要关注的不是这段代码实现的功能,而是这段代码出现的地方,也就是 main函数

二、万物始于main函数

  回顾曾经写过的代码,无论是复杂的微服务项目,还是一行简单的 System.out.println() ,代码的入口函数一定是main函数,这已经成为编写代码时无需质疑的定式。但作为一个有梦想的程序猿,做事要知其然也要知其所以然,下面就让我们一起来探究一下为何万物始于main函数。

1. 为什么是main函数

  众所周知,我们编写的Java文件都是运行在JVM虚拟机上面,也就是说程序的编译和运行都是要遵循JVM的规定,那么我们就来看一看JVM源码中是如何规定的。

  在JVM启动程序中定义了这样一个方法 int JNICALL JavaMain(void * args); ,在这个方法中确定了如何加载Java应用程序的入口类和入口方法,这里我们暂时省略其他代码,直接阅读一下加载入口方法的代码:

代码语言:javascript
复制
    /* 获取应用的main */
    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;
    }

    {    /* 确保main方法是公有的 */
        jint mods;
        jmethodID mid;
        jobject obj = (*env)->ToReflectedMethod(env, mainClass,
                                                mainID, JNI_TRUE);

        if( obj == NULL) { /* 抛出异常 */
            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;
    }

    /* 执行main方法 */
    (*env)->CallStaticVoidMethod(env, mainClass, mainID, mainArgs);

  在上面的代码中我们可以看到,JVM在启动过程中会根据指定的 MainClass 即初始类去获取该类中的 main 方法,同时这里也明确了main方法必须是静态的、公有的且参数列表为 String 数组。看到这里,想必大家应该明白为什么在编写Java程序时入口函数一定需要是main函数了。

2. main函数如何执行

  了解了为什么Java程序的入口方法一定是main方法,下面我们再来了解一下一个包含main方法的Java程序到底是如何被执行的。

  当我们在idea中去执行上述代码时,实际上执行的是这样一行命令:

代码语言:javascript
复制
java {类名}.java

  在上面这行命令中出现的 java 指令实际上是jdk提供的执行java程序的指令,指令后面紧跟着的文件名就是待执行的java程序。这行命令会启动 java.exec 这样一个可执行程序,在这个可执行程序中会执行 src/share/tools/launcher/java.c 文件中的main方法,进行JVM启动前的运行环境版本检查、配置初始化并创建一个JVM进程来执行Java程序,执行Java程序的过程就是上面代码展示的寻找并调用入口类的main方法。

  需要注意的是JVM执行的java程序是已经编译完成的 .class文件 ,也即在执行指令之处会执行 javac 指令对.java文件进行编译,然后在进行执行上述的操作。这里从上面获取java程序中main方法的代码中也可以看出来:

代码语言:javascript
复制
mainID = (*env)->GetStaticMethodID(env, mainClass, "main",
                                       "([Ljava/lang/String;)V");

  这里参数列表的写法就是编译后的二进制.class文件中的写法,有兴趣的同学可以通过idea自带的查看二进制文件的工具自行查看一下。

  接下来的逻辑就是我们日常了解到的JVM加载、链接和初始化类和接口的流程,这里就不做过多的展开。

3. Java程序的执行方式

  在日常的开发过程中,除了上面直接运行一个java文件,我们大部分情况都是将Java程序打包成一个jar包进行运行,这里从源码中也能得窥一二。

代码语言:javascript
复制
    if (jarfile != 0) {
      // 如果使用jar方式运行,则从对应jar中获取mainClass
        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 {
      // 直接指定入口mainClass的className
      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);
    }

  从上述的代码中可以看到,获取应用程序的主类分为两种情况,一种是通过获取jar包中 META-INF/MANIFEST.MF 文件中指定的Main-Class类名,一种是通过指定className来获取(也就是上面示例中方式)。

三、总结

  心血来潮重新回顾了一下当初只知其然不知其所以然的知识点,分析不一定到位,仅供大家参考。上面分析使用的jdk源码是基于openJDK官网提供的 hotspot 1.7版本的源码,有兴趣的同学可以到官网上面下载源码阅读学习一下。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-06-23,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Brucebat的伪技术鱼塘 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、前言
  • 二、万物始于main函数
    • 1. 为什么是main函数
      • 2. main函数如何执行
        • 3. Java程序的执行方式
        • 三、总结
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档