当前位置: 首页 > 工具软件 > JamVM > 使用案例 >

jamVM阅读(一)——解析参数

归星驰
2023-12-01

学习源码,是代码技术提升的一个重要手段,在java相关的内容中,有各类框架的源码、tomcat的源码、android源码、jdk标准库的源码和jvm的源码等各种值得学习的内容。在这些内容当中,jvm的源码是唯一一种纯用c、c++完成的,因此对于很多写习惯了java的程序员来说,阅读jvm是一件不那么容易的事情,而且目前大多数的jvm源码相当冗长,不易于阅读,所以为了简单的了解jvm的运行机制,我们选择了“麻雀虽小,五脏俱全”的jamvm来进行阅读。

首先,下载编译jamvm自行百度去,这篇文章只对jamvm的代码做简单理解

由于对jamvm还不是特别了解,我们简单地从主函数开始进行阅读

int main(int argc, char *argv[]) {
    Class *array_class, *main_class;
    Object *system_loader, *array;
    MethodBlock *mb;
    InitArgs args;
    int class_arg;
    char *cpntr;
    int status;
    int i;

    setDefaultInitArgs(&args);

    // 解析命令参数
    class_arg = parseCommandLine(argc, argv, &args);

    // 设置类栈
    args.main_stack_base = &array_class;


    if (!initVM(&args)) {
        /**
         * TODO: Read
         * 启动虚拟机, 失败打印
         */
        printf("Could not initialise VM.  Aborting.\n");
        exit(1);
    }

    if ((system_loader = getSystemClassLoader()) == NULL)
        // TODO 设置主classLoader
        goto error;

    // TODO 给主线程设置classLoader
    mainThreadSetContextClassLoader(system_loader);

    for (cpntr = argv[class_arg]; *cpntr; cpntr++)
        /**
         * 把class的参数, 如‘jamVM HelloWorld’的‘HelloWorld’赋值给cpntr,对于路径修改为斜杠
         */
        if (*cpntr == '.')
            *cpntr = '/';


    // TODO 查询主函数
    main_class = findClassFromClassLoader(argv[class_arg], system_loader);
    if (main_class != NULL)
        // TODO 初始化一个主函数
        initClass(main_class);

    if (exceptionOccurred())
        // TODO 大概是处理异常
        goto error;

    // TODO 下面一整段
    mb = lookupMethod(main_class, SYMBOL(main),
                      SYMBOL(_array_java_lang_String__V));

    if (mb == NULL || !(mb->access_flags & ACC_STATIC)) {
        signalException(java_lang_NoSuchMethodError, "main");
        goto error;
    }

    /* Create the String array holding the command line args */

    i = class_arg + 1;
    if ((array_class = findArrayClass(SYMBOL(array_java_lang_String))) &&
        (array = allocArray(array_class, argc - i, sizeof(Object *)))) {
        Object **args = ARRAY_DATA(array, Object*) - i;

        for (; i < argc; i++)
            if (!(args[i] = Cstr2String(argv[i])))
                break;

        /* Call the main method */
        if (i == argc)
            executeStaticMethod(main_class, mb, array);
    }

    error:
    /* ExceptionOccurred returns the exception or NULL, which is OK
       for normal conditionals, but not here... */
    if ((status = exceptionOccurred() ? 1 : 0))
        uncaughtException();

    /* Wait for all but daemon threads to die */
    mainThreadWaitToExitVM();
    // TODO 关闭虚拟机
    exitVM(status);

    /* Keep the compiler happy */
    return 0;
}

上面简单地进行了注释,我们根据这个流程做分析,首先这一篇分析解析参数。


// 解析命令参数
    class_arg = parseCommandLine(argc, argv, &args);
这里传入的参数有三个,前两个是main函数的参数,代表了命令行的参数个数和参数数组,第三个是运行的初始化参数

typedef struct InitArgs {
    int asyncgc;
    int verbosegc;
    int verbosedll;
    int verboseclass;

    int compact_specified; /* Whether compaction has been given on the
                              command line, and the value if it has */
    int do_compact;

    int trace_jni_sigs;

    char *classpath; //classpath字面意思

    char *bootpath;
    char *bootpath_a;
    char *bootpath_p;
    char *bootpath_c;
    char *bootpath_v;

    int java_stack;
    unsigned long min_heap; // 最小堆栈值
    unsigned long max_heap; // 最大堆栈值

    Property *commandline_props;
    int props_count;

    void *main_stack_base; // 运行的类栈

    /* JNI invocation API hooks */
    
    int (*vfprintf)(FILE *stream, const char *fmt, va_list ap);
    void (*exit)(int status);
    void (*abort)(void);

#ifdef INLINING
    unsigned int codemem;
    int replication_threshold;
    int profile_threshold;
    int branch_patching_dup;
    int branch_patching;
    int print_codestats;
    int join_blocks;
    int profiling;
#endif

#ifdef HAVE_PROFILE_STUBS
    int dump_stubs_profiles;
#endif
} InitArgs;
这个是这个初始化参数的结构体,注释了解析命令行参数需要用的参数的含义


然后看这个parseCommandLine函数

/**
 * 对jamVM命令的参数做出解析
 * @param argc 参数个数
 * @param argv 参数数组
 * @param args 初始化参数???
 * @return
 */
int parseCommandLine(int argc, char *argv[], InitArgs *args) {
    Property props[argc - 1];
    int is_jar = FALSE;
    int status = 0;
    int i;

    args->commandline_props = &props[0];

    for (i = 1; i < argc; i++) {
        /**
         * 遍历
         */
        if (*argv[i] != '-') {
            /**
             * 如果不是-开头,即不是操作位
             */
            if (args->min_heap > args->max_heap) {
                // 如果参数中最小堆栈值大于最大堆栈值就报错
                printf("Minimum heap size greater than max!\n");
                status = 1;
                goto exit;
            }

            if (args->props_count) {
                args->commandline_props = sysMalloc(args->props_count *
                                                    sizeof(Property));
                memcpy(args->commandline_props, &props[0], args->props_count *
                                                           sizeof(Property));
            }

            if (is_jar) {
                /**
                 * 如果是jar就设置classpath
                 */
                args->classpath = argv[i];
                argv[i] = "jamvm/java/lang/JarLauncher";
            }

            // 返回运行class的参数所在位置
            return i;
        }

先看第一部分,对参数进行遍历,这里进行了判断,根据参数是否以'-'开头判断参数是表示操作还是表示class类。

这一部分参数是不以'-'开头的,即参数是class或者jar之类的可执行的java文件,然后如注释所说,先判断一下下文解析结果中的一些参数是否合法,如最小栈值大于最大栈值,然后对于jar文件进行解析,将classpath赋值。最后返回改参数的下标以供下文使用。




第二部分自然是对于实际操作的参数,我们可以执行java命令(如果编译过jamVM的可以用jamvm命令),来查看下java的参数种类


shabi@shabi-C-H110M-K-PRO:~$ java
用法: java [-options] class [args...]
           (执行类)
   或  java [-options] -jar jarfile [args...]
           (执行 jar 文件)
其中选项包括:
    -d32	  使用 32 位数据模型 (如果可用)
    -d64	  使用 64 位数据模型 (如果可用)
    -server	  选择 "server" VM
    -zero	  选择 "zero" VM
    -dcevm	  选择 "dcevm" VM
                  默认 VM 是 server,
                  因为您是在服务器类计算机上运行。


    -cp <目录和 zip/jar 文件的类搜索路径>
    -classpath <目录和 zip/jar 文件的类搜索路径>
                  用 : 分隔的目录, JAR 档案
                  和 ZIP 档案列表, 用于搜索类文件。
    -D<名称>=<值>
                  设置系统属性
    -verbose:[class|gc|jni]
                  启用详细输出
    -version      输出产品版本并退出
    -version:<值>
                  警告: 此功能已过时, 将在
                  未来发行版中删除。
                  需要指定的版本才能运行
    -showversion  输出产品版本并继续
    -jre-restrict-search | -no-jre-restrict-search
                  警告: 此功能已过时, 将在
                  未来发行版中删除。
                  在版本搜索中包括/排除用户专用 JRE
    -? -help      输出此帮助消息
    -X            输出非标准选项的帮助
    -ea[:<packagename>...|:<classname>]
    -enableassertions[:<packagename>...|:<classname>]
                  按指定的粒度启用断言
    -da[:<packagename>...|:<classname>]
    -disableassertions[:<packagename>...|:<classname>]
                  禁用具有指定粒度的断言
    -esa | -enablesystemassertions
                  启用系统断言
    -dsa | -disablesystemassertions
                  禁用系统断言
    -agentlib:<libname>[=<选项>]
                  加载本机代理库 <libname>, 例如 -agentlib:hprof
                  另请参阅 -agentlib:jdwp=help 和 -agentlib:hprof=help
    -agentpath:<pathname>[=<选项>]
                  按完整路径名加载本机代理库
    -javaagent:<jarpath>[=<选项>]
                  加载 Java 编程语言代理, 请参阅 java.lang.instrument
    -splash:<imagepath>
                  使用指定的图像显示启动屏幕
有关详细信息, 请参阅 http://www.oracle.com/technetwork/java/javase/documentation/index.html。
shabi@shabi-C-H110M-K-PRO:~$ java -X
    -Xmixed           混合模式执行 (默认)
    -Xint             仅解释模式执行
    -Xbootclasspath:<用 : 分隔的目录和 zip/jar 文件>
                      设置搜索路径以引导类和资源
    -Xbootclasspath/a:<用 : 分隔的目录和 zip/jar 文件>
                      附加在引导类路径末尾
    -Xbootclasspath/p:<用 : 分隔的目录和 zip/jar 文件>
                      置于引导类路径之前
    -Xdiag            显示附加诊断消息
    -Xnoclassgc       禁用类垃圾收集
    -Xincgc           启用增量垃圾收集
    -Xloggc:<file>    将 GC 状态记录在文件中 (带时间戳)
    -Xbatch           禁用后台编译
    -Xms<size>        设置初始 Java 堆大小
    -Xmx<size>        设置最大 Java 堆大小
    -Xss<size>        设置 Java 线程堆栈大小
    -Xprof            输出 cpu 配置文件数据
    -Xfuture          启用最严格的检查, 预期将来的默认值
    -Xrs              减少 Java/VM 对操作系统信号的使用 (请参阅文档)
    -Xcheck:jni       对 JNI 函数执行其他检查
    -Xshare:off       不尝试使用共享类数据
    -Xshare:auto      在可能的情况下使用共享类数据 (默认)
    -Xshare:on        要求使用共享类数据, 否则将失败。
    -XshowSettings    显示所有设置并继续
    -XshowSettings:all
                      显示所有设置并继续
    -XshowSettings:vm 显示所有与 vm 相关的设置并继续
    -XshowSettings:properties
                      显示所有属性设置并继续
    -XshowSettings:locale
                      显示所有与区域设置相关的设置并继续

-X 选项是非标准选项, 如有更改, 恕不另行通知。

然后对照着这些参数去看源代码,先把源代码全文贴一下:

switch (parseCommonOpts(argv[i], args, FALSE)) {
            /**
             * 如果是操作位,针对不同的操作做出处理
             */
            case OPT_OK:
                break;

            case OPT_ERROR:
                status = 1;
                goto exit;

            case OPT_UNREC:
            default:
                if (strcmp(argv[i], "-?") == 0 ||
                    strcmp(argv[i], "-help") == 0) {
                    goto usage;

                } else if (strcmp(argv[i], "-X") == 0) {
                    showNonStandardOptions();
                    goto exit;

                } else if (strcmp(argv[i], "-version") == 0) {
                    showVersionAndCopyright();
                    goto exit;

                } else if (strcmp(argv[i], "-showversion") == 0) {
                    showVersionAndCopyright();

                } else if (strcmp(argv[i], "-fullversion") == 0) {
                    showFullVersion();
                    goto exit;

                } else if (strncmp(argv[i], "-verbose", 8) == 0) {
                    char *type = &argv[i][8];

                    if (*type == '\0' || strcmp(type, ":class") == 0)
                        args->verboseclass = TRUE;

                    else if (strcmp(type, ":gc") == 0 || strcmp(type, "gc") == 0)
                        args->verbosegc = TRUE;

                    else if (strcmp(type, ":jni") == 0)
                        args->verbosedll = TRUE;

                } else if (strcmp(argv[i], "-jar") == 0) {
                    is_jar = TRUE;

                } else if (strcmp(argv[i], "-classpath") == 0 ||
                           strcmp(argv[i], "-cp") == 0) {

                    if (i == argc - 1) {
                        printf("%s : missing path list\n", argv[i]);
                        goto exit;
                    }
                    args->classpath = argv[++i];

                } else if (strncmp(argv[i], "-Xbootclasspath/c:", 18) == 0) {
                    args->bootpath_c = argv[i] + 18;

                } else if (strncmp(argv[i], "-Xbootclasspath/v:", 18) == 0) {
                    args->bootpath_v = argv[i] + 18;

                    /* Compatibility options */
                } else if (strcmp(argv[i], "-client") == 0 ||
                           strcmp(argv[i], "-server") == 0) {
                    /* Ignore */
                } else {
                    printf("Unrecognised command line option: %s\n", argv[i]);
                    status = 1;
                    goto usage;
                }
        }
    }

    usage:
    showUsage(argv[0]);

    exit:
    exit(status);
}

这是parseCommandLine函数的后半段,很显然一开始执行了parseCommonOpts方法,并对返回的结果进行判断,如果成功就break这个switch,如果失败就跳转到exit函数,这个exit函数顾名思义就是退出,一会再看,然后对于其他一些特殊的如-version这样的参数就输出指定的结果,然后执行showUsage函数,showUsage是输出一个模板,即:

void showUsage(char *name) {
    printf("Usage: %s [-options] class [arg1 arg2 ...]\n", name);
    printf("                 (to run a class file)\n");
    printf("   or  %s [-options] -jar jarfile [arg1 arg2 ...]\n", name);
    printf("                 (to run a standalone jar file)\n");
    printf("\nwhere options include:\n");
    printf("  -client\t   compatibility (ignored)\n");
    printf("  -server\t   compatibility (ignored)\n\n");
    printf("  -cp\t\t   <jar/zip files and directories separated by :>\n");
    printf("  -classpath\t   <jar/zip files and directories separated by :>\n");
    printf("\t\t   locations where to find application classes\n");
    printf("  -D<name>=<value> set a system property\n");
    printf("  -verbose[:class|gc|jni]\n");
    printf("\t\t   :class print out information about class loading, etc.\n");
    printf("\t\t   :gc print out results of garbage collection\n");
    printf("\t\t   :jni print out native method dynamic resolution\n");
    printf("  -version\t   print out version number and copyright information\n");
    printf("  -showversion     show version number and copyright and continue\n");
    printf("  -fullversion     show jpackage-compatible version number and exit\n");
    printf("  -? -help\t   print out this message\n");
    printf("  -X\t\t   show help on non-standard options\n");
}

这段就不解释了,我们来看看核心函数parseCommandLine:

/**
 * 解析操作位
 * @param string 操作位 如“-version”
 * @param args 初始化参数
 * @param is_jni 是否是jni的调用
 * @return
 */
int parseCommonOpts(char *string, InitArgs *args, int is_jni) {
    /**
     * 初始返回值
     */
    int status = OPT_OK;

    if(strcmp(string, "-Xasyncgc") == 0)
        args->asyncgc = TRUE;

    else if(strncmp(string, "-Xms", 4) == 0 ||
            (!is_jni && strncmp(string, "-ms", 3) == 0)) {

        /**
         * 将指针指向参数后的数字
         */
        char *value = string + (string[1] == 'm' ? 3 : 4);

        // 将初始化堆栈的大小赋值
        args->min_heap = parseMemValue(value);

        if(args->min_heap < MIN_HEAP) {
            // 如果数据不合法就返回错误
            optError(args, "Invalid minimum heap size: %s (min %dK)\n",
                     string, MIN_HEAP/KB);
            status = OPT_ERROR;
        }

    } else if(strncmp(string, "-Xmx", 4) == 0 ||
              (!is_jni && strncmp(string, "-mx", 3) == 0)) {

        char *value = string + (string[1] == 'm' ? 3 : 4);
        args->max_heap = parseMemValue(value);

        if(args->max_heap < MIN_HEAP) {
            optError(args, "Invalid maximum heap size: %s (min is %dK)\n",
                     string, MIN_HEAP/KB);
            status = OPT_ERROR;
        }

    } else if(strncmp(string, "-Xss", 4) == 0 ||
              (!is_jni && strncmp(string, "-ss", 3) == 0)) {

        char *value = string + (string[1] == 'm' ? 3 : 4);
        args->java_stack = parseMemValue(value);

        if(args->java_stack < MIN_STACK) {
            optError(args, "Invalid Java stack size: %s (min is %dK)\n",
                     string, MIN_STACK/KB);
            status = OPT_ERROR;
        }

    } else if(strncmp(string, "-D", 2) == 0) {
        char *key = strcpy(sysMalloc(strlen(string + 2) + 1), string + 2);
        char *pntr;

        for(pntr = key; *pntr && (*pntr != '='); pntr++);
        if(*pntr)
            *pntr++ = '\0';
        args->commandline_props[args->props_count].key = key;
        args->commandline_props[args->props_count++].value = pntr;

    } else if(strncmp(string, "-Xbootclasspath:", 16) == 0) {
        args->bootpath = string + 16;
        args->bootpath_p = args->bootpath_a = NULL;

    } else if(strncmp(string, "-Xbootclasspath/a:", 18) == 0) {
        args->bootpath_a = string + 18;

    } else if(strncmp(string, "-Xbootclasspath/p:", 18) == 0) {
        args->bootpath_p = string + 18;

    } else if(strcmp(string, "-Xnocompact") == 0) {
        args->compact_specified = TRUE;
        args->do_compact = FALSE;

    } else if(strcmp(string, "-Xcompactalways") == 0) {
        args->compact_specified = args->do_compact = TRUE;

    } else if(strcmp(string, "-Xtracejnisigs") == 0) {
        args->trace_jni_sigs = TRUE;
#ifdef INLINING
    } else if(strcmp(string, "-Xnoinlining") == 0) {
        /* Turning inlining off is equivalent to setting
           code memory to zero */
        args->codemem = 0;

    } else if(strcmp(string, "-Xnoprofiling") == 0) {
        args->profiling = FALSE;

    } else if(strcmp(string, "-Xnopatching") == 0) {
        args->branch_patching = FALSE;

    } else if(strcmp(string, "-Xnopatchingdup") == 0) {
        args->branch_patching_dup = FALSE;

    } else if(strcmp(string, "-Xnojoinblocks") == 0) {
        args->join_blocks = FALSE;

    } else if(strcmp(string, "-Xcodestats") == 0) {
        args->print_codestats = TRUE;

    } else if(strncmp(string, "-Xprofiling:", 12) == 0) {
        args->profile_threshold = strtol(string + 12, NULL, 0);

    } else if(strncmp(string, "-Xreplication:", 14) == 0) {
        char *pntr = string + 14;

        if(strcmp(pntr, "none") == 0)
            args->replication_threshold = INT_MAX;
        else
            if(strcmp(pntr, "always") == 0)
                args->replication_threshold = 0;
            else
                args->replication_threshold = strtol(pntr, NULL, 0);

    } else if(strncmp(string, "-Xcodemem:", 10) == 0) {
        char *pntr = string + 10;

        args->codemem = strncmp(pntr, "unlimited", 10) == 0 ?
            INT_MAX : parseMemValue(pntr);

    } else if(strcmp(string, "-Xshowreloc") == 0) {
        showRelocatability();
#endif

#ifdef HAVE_PROFILE_STUBS
    } else if(strcmp(string, "-Xdumpstubsprofiles") == 0) {
        args->dump_stubs_profiles = TRUE;
#endif
    /* Compatibility options */
    } else {
        int i;

        for(i = 0; compat[i].option != NULL; i++) {
            int len = strlen(compat[i].option);

            if(strncmp(string, compat[i].option, len) == 0 &&
               (((compat[i].flags & OPT_ARG) && string[len] == ':') ||
                ((compat[i].flags & OPT_NOARG) && string[len] == '\0')))
                break;
        }

        if(compat[i].option == NULL)
            status = OPT_UNREC;
    }

    return status;
}

代码相当长,但其实只是对于不同的命令做分别的处理,我们简单看一种命令,看一下上面代码注释了的那一段:

    else if(strncmp(string, "-Xms", 4) == 0 ||
            (!is_jni && strncmp(string, "-ms", 3) == 0)) {

        /**
         * 将指针指向参数后的数字
         */
        char *value = string + (string[1] == 'm' ? 3 : 4);

        // 将初始化堆栈的大小赋值
        args->min_heap = parseMemValue(value);

        if(args->min_heap < MIN_HEAP) {
            // 如果数据不合法就返回错误
            optError(args, "Invalid minimum heap size: %s (min %dK)\n",
                     string, MIN_HEAP/KB);
            status = OPT_ERROR;
        }

查看命令输出我们可以看到-Xms是设置初始化堆栈大小的,首先将指针移到命令后面 即 ‘-Xms 100M’移到 '100M',然后调parseMemValue函数,将返回结果赋值给args,并且判断是否合法


/**
 * 解析参数后的那个数
 * @param str
 * @return
 */
unsigned long parseMemValue(char *str) {
    char *end;
    /**
     * 将参数转化为number,其中end储存第一个不能被转换的字符指针,第三个参数为0表示10进制
     */
    unsigned long n = strtol(str, &end, 0);

    switch(end[0]) {
        case '\0':
            break;

        case 'G': case 'g':
            n *= KB;

        case 'M': case 'm':
            n *= KB;

        case 'K': case 'k':
            n *= KB;

            if(end[1] == '\0')
                break;

        default:
             n = 0;
    }

    return n;
}

这个函数很好理解,注释都有就不解释了


这就是jamVM解析命令行参数的全部流程了,这里只是将命令行的参数解析赋值到了args变量中,而args变量相当于执行过程的全局变量,而真正的执行过程在后面的文章中解析。








 类似资料: