学习源码,是代码技术提升的一个重要手段,在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或者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);
}
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");
}
/**
* 解析操作位
* @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;
}
/**
* 解析参数后的那个数
* @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变量相当于执行过程的全局变量,而真正的执行过程在后面的文章中解析。